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

  1. Use meaningful collection names - Use snake_case for multi-word names (e.g., inventory_items)

  2. Plan your schema - Design your content types and relationships before creating them

  3. Use draft/publish for content - Enable draftAndPublish for content that needs review

  4. Set appropriate constraints - Use required, unique, and validation options

  5. Index frequently queried fields - Fields marked as unique are automatically indexed

  6. 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