Skip to main content

Context

Pages, forms, posts and many other authored things share drafts, versions, publishing, slugs, SEO, translation, permissions and search. One package per type would repeat all of that. A generic JSON store for every type would be the “JSONB-for-everything” that 0011 rejects. Tenants also want extra fields on both content and business records (a Student, an Order).

Decision

  1. CT1 — @stackra/content is the free content core. Page, form definition and post are built-in content types sharing drafts, versions, publishing, slugs, SEO, i18n, permissions, search and the document format of 0110. New types are declared in code with @ContentType in any module.
  2. CT3 — @ContentType is @Model plus a trait bundle: HasRevisions, Publishable, Sluggable, HasSeo, Translatable, Searchable, HasCustomFields and routes (0096). Each type is a real, typed table generated by gen (0097); there is no generic JSON store.
  3. The decision rule:
  4. CT4 — Built-in content types:
    • page, post, form definition, gallery;
    • help article, FAQ, announcement, changelog;
    • legal document (acceptance feeds consent), testimonial, team member, job posting;
    • reusable blocks, remote navigation menus (which feed @stackra/navigation), redirects and short links.
    Submissions, products, courses, orders and bookings are domain models, not content.
  5. CT2 — HasCustomFields is free and bounded, on content types and on business models. Values live in a side table and are validated by the stored field schema; how many fields a tenant may add is a plan limit enforced through entitlements.
  6. Tenant-defined new content types at runtime are Pro and later, and need their own amendment of 0011 first.

Consequences

  • Every content type gets publishing, SEO and search for free, and stays a typed table that migrations and RLS understand.
  • The decision rule gives reviewers a test for “should this be content”.
  • Custom fields add a side-table join to reads; search and list filtering must learn about them.

Enforcement

Planned: the gen validation pass fails a @ContentType that also declares a state machine or money field without the domain-model form, and a HasCustomFields model with no entitlement limit key. Nothing checks this today.

Supersedes and sources

Supersedes: none. Sources: owner decisions in chat, 2026-10-19 (Round 11 CT1, CT2; “LOCKED content/workflow studio” CT3, CT4).