Skip to content
On this page

Post Types API ​

The Post Types API manages custom content type definitions (CPT) and pairs with the Blog posts API for typed custom_fields.

This page is written to help developers enable CPT safely and call the right routes quickly.

How to read this page ​

Each route is also documented as an operation page under Post Types operations:

  • what it does
  • who can call it
  • request shape
  • response and error codes

Base path ​

All CPT definition routes live under /api/v1/post-types.

Related Blog routes live under /api/v1/posts (?type=, body post_type / custom_fields).

Enable first ​

Installing the Module Hub schema does not turn CPT on.

bash
BLOG_ENABLED=true
POST_TYPES_ENABLED=true
# optional MCU: POST_TYPES_MCU=post-types://enabled

If disabled, every /api/v1/post-types call returns 503 with code: MODULE_DISABLED.

Install schema (after Blog). Prefer dry-run first, then run. Always send Content-Type: application/json and plain ASCII JSON (no rich-text / NBSP spaces from copy-paste).

http
POST /api/v1/modules/install/post-types/dry-run
Content-Type: application/json
X-Session-ID: <session>
json
{
  "target": "all",
  "runtime_scope": "flowless-core"
}
http
POST /api/v1/modules/install/post-types
Content-Type: application/json
X-Session-ID: <session>
json
{
  "target": "all",
  "runtime_scope": "flowless-core"
}

target: "all" installs core + ext-seo-per-type + ext-type-restrictions. target: "core" installs only core (ignores components). Empty body {} is valid and defaults to target: "core".

Error messageMeaning
Invalid JSON bodyBody failed JSON.parse (NBSP/smart quotes, bad Content-Type, truncated JSON)
Invalid input (+ fields)JSON parsed, but Zod rejected a field

See Module Install Dry Run.

Sample type definitions are documented on the Post Types Examples page — Module Hub never auto-installs them.

What the API is for ​

  • Listing and reading type definitions
  • Creating / updating / deleting types (admin)
  • Fetching public field_schema for admin UIs
  • Stats per type (admin)
  • Listing posts of a type via proxy

Route map ​

MethodPathAuthPurpose
GET/api/v1/post-typesPublicList types (?active=false includes inactive)
POST/api/v1/post-typesSession + CPT adminCreate a type definition
GET/api/v1/post-types/{key}PublicGet one definition
PUT/api/v1/post-types/{key}Session + CPT adminUpdate definition (type_key immutable)
DELETE/api/v1/post-types/{key}Session + CPT adminDelete (not built-in; not if posts exist)
GET/api/v1/post-types/{key}/schemaPublicSchema for editors (field_schema, behaviors, icon…)
GET/api/v1/post-types/{key}/statsSession + CPT adminCounts by status, lang, author
GET/api/v1/post-types/{key}/postsOptionalInternal proxy → GET /api/v1/posts?type={key}

Blog routes used with CPT ​

MethodPathCPT fields
GET/api/v1/postsQuery type (+ lang, filters)
POST/api/v1/postsBody post_type, custom_fields
PUT/api/v1/posts/{id}Body custom_fields (post_type locked)

See Blog operations.

Example: list types ​

http
GET /api/v1/post-types
json
{
  "data": [
    {
      "id": "ptd_1",
      "type_key": "case-study",
      "type_name": "Case Study",
      "field_schema": [],
      "behaviors": { "enables_scheduling": true },
      "is_active": true,
      "is_built_in": false,
      "sort_order": 100
    }
  ],
  "meta": { "total": 1 }
}

Example: create type ​

http
POST /api/v1/post-types
X-Session-ID: <session>
Content-Type: application/json
json
{
  "type_key": "case-study",
  "type_name": "Case Study",
  "description": "Customer success / case study",
  "slug_prefix": "case-studies/",
  "icon": "briefcase",
  "color": "#10B981",
  "is_active": true,
  "sort_order": 10,
  "field_schema": [
    {
      "name": "client_name",
      "label": "Client Name",
      "type": "text",
      "required": true,
      "max_length": 120
    },
    {
      "name": "industry",
      "label": "Industry",
      "type": "select",
      "required": false,
      "options": ["Tech", "Finance", "Healthcare", "Retail", "Education", "SaaS", "E-commerce"]
    },
    {
      "name": "challenge",
      "label": "The Challenge",
      "type": "textarea",
      "required": false
    },
    {
      "name": "solution",
      "label": "Our Solution",
      "type": "textarea",
      "required": false
    },
    {
      "name": "results",
      "label": "Results Achieved",
      "type": "textarea",
      "required": true
    },
    {
      "name": "duration",
      "label": "Project Duration",
      "type": "text",
      "required": false
    },
    {
      "name": "project_url",
      "label": "Live Project URL",
      "type": "url",
      "required": false
    },
    {
      "name": "technologies",
      "label": "Technologies Used",
      "type": "text",
      "required": false
    }
  ],
  "behaviors": {
    "public_by_default": true,
    "enables_seo": true,
    "enables_comments": false,
    "enables_reactions": true,
    "enables_bookmarks": true,
    "enables_views": true,
    "enables_scheduling": true,
    "requires_approval": true,
    "allows_password_visibility": false,
    "default_visibility": "public",
    "allowed_body_formats": ["markdown", "html", "tiptap_json"]
  }
}

Full sample catalog (7 types): Post Types Examples.

Example: schema for editors ​

http
GET /api/v1/post-types/case-study/schema

Returns field_schema, behaviors, icon, slug_prefix, color for dynamic forms.

field_schema item shape ​

json
{
  "name": "client_name",
  "label": "Client Name",
  "type": "text",
  "required": true,
  "max_length": 120,
  "placeholder": "ACME",
  "options": ["Tech", "Finance"]
}

Types: text | textarea | number | url | date | select | multi-select | boolean

Behaviors (common) ​

KeyEffect
requires_approvalNon-admins cannot publish
allows_password_visibilityAllow visibility=password
default_visibilityUsed when visibility omitted on create
enables_schedulingAllow scheduled_at / schedule
allowed_body_formatsRestrict body_format
max_per_authorCap posts of this type per author

Error codes ​

CodeWhen
MODULE_DISABLEDCPT env/MCU off (503)
USER_TYPES_DISABLEDPOST_TYPES_ALLOW_USER_TYPES=false
POST_TYPE_LOCKEDChanging post_type after create
APPROVAL_REQUIREDNon-admin publish when approval required
MAX_PER_AUTHORAuthor over type quota
CUSTOM_FIELDS_TOO_LARGE / validation errorsBad or oversized custom_fields

Env reference ​

VarDefaultNotes
POST_TYPES_ENABLEDfalseMaster switch
POST_TYPES_MCU``post-types://enabled@?...
POST_TYPES_REQUIRE_APPROVALfalseGlobal approval
POST_TYPES_DEFAULT_TYPEpostDefault post_type
POST_TYPES_ALLOW_USER_TYPEStrueAllow create via API
POST_TYPES_CUSTOM_FIELDS_MAX_KB10Cap
POST_TYPES_FIELD_SCHEMA_MAX_KB64Cap
POST_TYPES_ADMIN_ROLES(blog admin)Type CRUD + publish under approval
POST_TYPES_CACHE_TTL86400Def cache TTL
POST_TYPES_FRESH_CACHE_MODEtrueBypass Redis when true

Operation pages ​

Next ​