TGM Manager includes a powerful headless CMS that allows you to create entirely new database tables dynamically at runtime. This is ideal for storing custom data models without modifying the core application schema.
Overview¶
Custom tables (content types) let you:
- Define new data structures with custom fields
- Automatically generate database tables
- Access data via a consistent REST API
- Support draft/publish workflows
- Create relationships between tables
Field Types¶
| Type | SQL Type | Description |
|---|---|---|
string |
VARCHAR(n) | Short text with configurable length |
text |
TEXT | Long text, unlimited |
richtext |
TEXT | Rich text/HTML content |
integer |
INTEGER | Whole numbers |
biginteger |
BIGINT | Large whole numbers |
float |
REAL | Floating point numbers |
decimal |
DECIMAL(10,2) | Precise decimal numbers |
boolean |
BOOLEAN | True/false values |
date |
DATE | Date only |
time |
TIME | Time only |
datetime |
TIMESTAMP | Date and time |
json |
JSONB | JSON data |
enumeration |
VARCHAR(100) | Predefined values |
email |
VARCHAR(255) | Email addresses |
uid |
VARCHAR(255) | Unique identifiers |
media |
BIGINT | File reference |
relation |
BIGINT | Foreign key reference |
REST API¶
Content Type Management¶
List All Content Types¶
GET /api/cms/content-types
Response:
[
{
"kind": "collectionType",
"collectionName": "products",
"info": {
"singularName": "product",
"pluralName": "products",
"displayName": "Product"
},
"options": {
"draftAndPublish": true
},
"attributes": {
"name": { "type": "string", "required": true, "maxLength": 255 },
"price": { "type": "decimal", "required": true },
"description": { "type": "richtext" }
}
}
]
Get Content Type Schema¶
GET /api/cms/content-types/{collectionName}
Count Content Types¶
GET /api/cms/content-types/count
Create Content Type¶
POST /api/cms/content-types
Content-Type: application/json
{
"collectionName": "products",
"info": {
"singularName": "product",
"pluralName": "products",
"displayName": "Product"
},
"options": {
"draftAndPublish": true
},
"attributes": {
"name": {
"type": "string",
"required": true,
"maxLength": 255
},
"sku": {
"type": "string",
"unique": true,
"required": true
},
"price": {
"type": "decimal",
"required": true,
"min": 0
},
"description": {
"type": "richtext"
},
"inStock": {
"type": "boolean",
"default": true
},
"category": {
"type": "enumeration",
"enum": ["Electronics", "Clothing", "Food", "Other"]
}
}
}
Response:
{
"success": true,
"message": "Content type created successfully",
"collectionName": "products",
"schema": { ... }
}
Delete Content Type¶
Destructive Operation
This permanently deletes the database table and all its data!
DELETE /api/cms/content-types/{collectionName}
Data Operations (CRUD)¶
List Records¶
GET /api/cms/{collectionName}
Parameters:
- page (optional) - Page number for pagination
- pageSize (optional) - Records per page
- sort (optional) - Field to sort by
- order (optional) - Sort order: ASC or DESC (default: DESC)
Response:
{
"data": [
{
"id": 1,
"name": "Laptop",
"sku": "LAP-001",
"price": 999.99,
"inStock": true,
"created_at": "2024-01-15T10:30:00",
"updated_at": "2024-01-15T10:30:00"
}
],
"meta": {
"pagination": {
"page": 1,
"pageSize": 25,
"pageCount": 4,
"total": 100
}
}
}
Get Single Record¶
GET /api/cms/{collectionName}/{id}
Create Record¶
POST /api/cms/{collectionName}
Content-Type: application/json
{
"name": "Smartphone",
"sku": "PHN-002",
"price": 599.99,
"description": "<p>Latest smartphone model</p>",
"inStock": true,
"category": "Electronics"
}
Update Record¶
PUT /api/cms/{collectionName}/{id}
Content-Type: application/json
{
"price": 549.99,
"inStock": false
}
Delete Record¶
DELETE /api/cms/{collectionName}/{id}
Bulk Delete Records¶
POST /api/cms/{collectionName}/bulk-delete
Content-Type: application/json
{
"ids": [1, 2, 3, 4, 5]
}
Search and Filter¶
POST /api/cms/{collectionName}/search
Content-Type: application/json
{
"category": "Electronics",
"inStock": true
}
Parameters:
- page, pageSize, sort, order - Same as list endpoint
Count Records¶
GET /api/cms/{collectionName}/count
Draft and Publish Workflow¶
When draftAndPublish is enabled for a content type:
Publish Record¶
POST /api/cms/{collectionName}/{id}/publish
Unpublish Record¶
POST /api/cms/{collectionName}/{id}/unpublish
Published records have:
- published_at - Publication timestamp
- published_by_id - User who published
- publication_status - DRAFT or PUBLISHED
Schema Synchronization¶
Sync all schema definitions with database tables:
POST /api/cms/migrations/sync
Response:
{
"success": true,
"successes": ["products", "categories"],
"errors": [],
"warnings": [],
"skipped": []
}
Field Definition Options¶
Common Options¶
{
"type": "string",
"required": true,
"unique": true,
"private": false,
"default": "Default Value"
}
String Options¶
{
"type": "string",
"minLength": 5,
"maxLength": 100,
"regex": "^[A-Z].*"
}
Number Options¶
{
"type": "decimal",
"min": 0,
"max": 10000
}
Enumeration Options¶
{
"type": "enumeration",
"enum": ["Option1", "Option2", "Option3"]
}
Relation Options¶
{
"type": "relation",
"relation": "manyToOne",
"target": "categories"
}
Relation types:
- oneToOne - One-to-one relationship
- oneToMany - One-to-many relationship
- manyToOne - Many-to-one relationship
- manyToMany - Many-to-many relationship
Media Options¶
{
"type": "media",
"multiple": true,
"allowedTypes": ["images", "files"]
}
Usage Examples¶
Creating a Blog System¶
# Create Categories table
curl -X POST http://localhost:1337/api/cms/content-types \
-H "Authorization: Bearer $JWT" \
-H "Content-Type: application/json" \
-d '{
"collectionName": "categories",
"info": {
"singularName": "category",
"pluralName": "categories",
"displayName": "Category"
},
"attributes": {
"name": { "type": "string", "required": true, "unique": true },
"slug": { "type": "uid", "required": true, "unique": true },
"description": { "type": "text" }
}
}'
# Create Articles table with relation to categories
curl -X POST http://localhost:1337/api/cms/content-types \
-H "Authorization: Bearer $JWT" \
-H "Content-Type: application/json" \
-d '{
"collectionName": "articles",
"info": {
"singularName": "article",
"pluralName": "articles",
"displayName": "Article"
},
"options": {
"draftAndPublish": true
},
"attributes": {
"title": { "type": "string", "required": true, "maxLength": 255 },
"slug": { "type": "uid", "required": true, "unique": true },
"content": { "type": "richtext", "required": true },
"excerpt": { "type": "text", "maxLength": 500 },
"category": {
"type": "relation",
"relation": "manyToOne",
"target": "categories"
},
"featuredImage": {
"type": "media",
"allowedTypes": ["images"]
},
"tags": { "type": "json" }
}
}'
Creating an Inventory System¶
curl -X POST http://localhost:1337/api/cms/content-types \
-H "Authorization: Bearer $JWT" \
-H "Content-Type: application/json" \
-d '{
"collectionName": "inventory_items",
"info": {
"singularName": "inventoryItem",
"pluralName": "inventoryItems",
"displayName": "Inventory Item"
},
"attributes": {
"name": { "type": "string", "required": true },
"sku": { "type": "string", "unique": true, "required": true },
"quantity": { "type": "integer", "required": true, "min": 0 },
"reorderLevel": { "type": "integer", "default": 10 },
"unitCost": { "type": "decimal", "required": true },
"location": { "type": "string" },
"lastRestocked": { "type": "datetime" },
"notes": { "type": "text" }
}
}'
Adding Records¶
# Add a category
curl -X POST http://localhost:1337/api/cms/categories \
-H "Authorization: Bearer $JWT" \
-H "Content-Type: application/json" \
-d '{
"name": "Technology",
"slug": "technology",
"description": "Tech news and reviews"
}'
# Add an article
curl -X POST http://localhost:1337/api/cms/articles \
-H "Authorization: Bearer $JWT" \
-H "Content-Type: application/json" \
-d '{
"title": "Getting Started with TGM Manager",
"slug": "getting-started-tgm",
"content": "<p>Welcome to TGM Manager...</p>",
"excerpt": "Learn how to get started with TGM Manager",
"category": 1,
"tags": ["tutorial", "beginner"]
}'
# Publish the article
curl -X POST http://localhost:1337/api/cms/articles/1/publish \
-H "Authorization: Bearer $JWT"
Database Structure¶
Each content type creates a table with the prefix cms_:
CREATE TABLE cms_products (
id BIGSERIAL PRIMARY KEY,
created_at TIMESTAMP WITHOUT TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITHOUT TIME ZONE,
created_by_id BIGINT,
updated_by_id BIGINT,
published_at TIMESTAMP WITHOUT TIME ZONE,
published_by_id BIGINT,
publication_status VARCHAR(20) DEFAULT 'DRAFT',
name VARCHAR(255) NOT NULL,
sku VARCHAR(255) NOT NULL UNIQUE,
price DECIMAL(10,2) NOT NULL,
description TEXT,
in_stock BOOLEAN DEFAULT false,
category VARCHAR(100)
);
Best Practices¶
-
Use meaningful collection names - Use snake_case for multi-word names (e.g.,
inventory_items) -
Plan your schema - Design your content types and relationships before creating them
-
Use draft/publish for content - Enable
draftAndPublishfor content that needs review -
Set appropriate constraints - Use
required,unique, and validation options -
Index frequently queried fields - Fields marked as
uniqueare automatically indexed -
Backup before deleting - Deleting a content type permanently removes all data
Comparison: Custom Fields vs Custom Tables¶
| Feature | Custom Fields | Custom Tables |
|---|---|---|
| Purpose | Extend existing entities | Create new data models |
| Storage | JSONB column on entity | Separate database table |
| Schema | Flexible, per entity | Strict, defined upfront |
| Relationships | No | Yes (foreign keys) |
| Indexing | Limited | Full support |
| Query performance | Slower for large datasets | Optimized |
| Use case | Ad-hoc attributes | Structured data |