# Content types and extensions

Build custom collections, bind fields and release reusable layouts through the administrator interface.

GroCMS 0.1.0 · Workspace · Editors reviewing collections and developers extending the CMS · Reviewed 15 September 2026

## Content types describe structured collections

A content type defines the common shape and behavior of a collection. Posts and Pages are the default collections, protected from retirement and deletion. They share title, body, excerpt, visibility, media and publication behavior, while custom types can add scalar fields and permitted taxonomies. Upgraded installations retain legacy Events and Vehicles under Retired types, with entries and history preserved and website content withdrawn.

Administrators can create custom types in Content types, add supported scalar fields, save a definition draft and review its activation assessment. Activation makes that exact version available to forms, APIs and routes. Built-in definitions remain read-only. After activation, stored keys, field kinds and route capabilities are fixed; labels, help text, order, optional fields, appended choices and relaxed constraints can change compatibly.

| Type | Public path | Distinct behavior |
| --- | --- | --- |
| Posts | /journal | Archive, search, categories and tags |
| Pages | Root and nested paths | Parent hierarchy; no ordinary type archive |

Sources: Installed type and taxonomy registry (repository: `apps/cms/src/cms/definitions/index.ts`); TypeDefinition and supported scalar fields (repository: `apps/cms/src/cms/contracts.ts`); Content type builder and catalogue (repository: `apps/cms/src/components/admin/type-builder.tsx`).

## Draft and activate a collection

Choose Create content type as an administrator. Set labels, the permanent type key and route base, supported taxonomies and public/archive/search behavior. Add fields, defaults and validation limits, then choose Save draft, Check impact, Activate saved version and Activate. The route base must be unique and avoid reserved application paths. Existing editors receive a conflict if a definition changes while they are editing; their values are retained for review.

```typescript
{ key: "recipe", label: "Recipes", singular: "Recipe",
  routeBase: "recipes", archive: true, hierarchical: false,
  public: true, searchable: true,
  fields: [
    { key: "preparation", label: "Preparation", type: "text", required: true },
    { key: "minutes", label: "Minutes", type: "number", required: true }
  ],
  taxonomies: ["category", "tag"],
  description: "Recipes with preparation notes and cooking time." }
```

This example shows the definition shape used by the administrator services. The existing resource API, forms and public loader handle the activated collection. A source-code declaration alone does not register a custom type in an existing database. Relationships, repeaters, computed fields, new taxonomies and specialized indexed queries remain developer extensions.

Sources: Installed type and taxonomy registry (repository: `apps/cms/src/cms/definitions/index.ts`); TypeDefinition and supported scalar fields (repository: `apps/cms/src/cms/contracts.ts`); Content-type and component extension contract (repository: `docs/developer-guide.md`).

## Retire, restore or permanently delete a collection

Retire type withdraws a collection and its entries from the website while keeping its entries, translations and history. Review the impact and resolve listed dependencies before confirming. Open Retired types and View retained content to inspect its read-only entries. Restore type enables editing again; it does not republish entries or reinstate cancelled publishing schedules. Review and publish entries separately.

Delete permanently removes the type's definitions and all its authored entries, translations, trash and revision content. Resolve current draft, published, scheduled and available reusable-component dependencies, check again and type the exact permanent key to confirm. Shared media, other types, historical snapshots belonging to other resources and audit/publication evidence are retained. The deleted key remains reserved. Posts and Pages are protected. These actions require an administrator session and are unavailable to API keys.

Historical references in other resources may remain after deletion. Their JSON stays unchanged. Deleted entry and type references show notices; original URL links may stop working. Choose Load for repair to copy a historical revision into an unsaved draft based on the current working revision. Autosave pauses, including after tab recovery. Remove or replace deleted references and choose Save changes; unresolved references cannot be saved or restored. Publishing is a separate step.

A changed impact assessment requires fresh review and confirmation. Retrying after a lost response reuses the same operation. Permanent deletion cannot remove copies already stored in earlier exports or backups.

Sources: Content type builder and catalogue (repository: `apps/cms/src/components/admin/type-builder.tsx`); Content type retirement and permanent deletion (repository: `apps/cms/src/cms/services/type-lifecycle.ts`); Archive route registration (repository: `apps/cms/src/cms/services/content.ts`).

## Validation and presentation are separate decisions

Supported fields include text, number, boolean, calendar date, datetime, select, rich text, typed links, media, single or ordered multiple relationships, and bounded scalar lists. A select declares its allowed options. Relationships target one declared kind/type in the entry language. Lists and multiple relationships allow at most 100 items; models allow at most 50 fields. Posts and Pages accept compatible optional custom fields. Required fields may remain empty in a saved draft but must be valid before publication; unknown field keys are rejected. Date-time values represent instants with an offset, with the interface helping the editor choose a local time. Cross-field rules, such as an event ending after it starts, belong in server validation rather than only in form controls.

Add Content field to a visual composition and choose a type, field and compatible text, number, currency, date or yes/no format. Bindings read the current entry, using its language and the site timezone. Required missing values block publication; optional fields can use a fallback. Use current-entry bindings in content or single:TYPE templates (page for pages). Unscoped shared sections and archive/index templates cannot use them in this release. Numeric fields do not automatically add range filters or specialized sorting.

Sources: TypeDefinition and supported scalar fields (repository: `apps/cms/src/cms/contracts.ts`); Field, composition and reference validation (repository: `apps/cms/src/cms/validation.ts`); Component rendering and fields (repository: `apps/cms/src/cms/components/config.tsx`); Content-type and component extension contract (repository: `docs/developer-guide.md`).

## Register routes and preserve role boundaries

Definition activation registers archive routes for the configured languages in the same transaction. The seedRoutes command below remains a setup or repair operation for an authorized operator. Route bases are fixed after activation; changing one needs a reviewed migration and redirect plan. Keep private environments and project names aligned with the intended target.

```powershell
docker compose --env-file .env.production.local --project-name gcms run --rm --no-deps migrate node --import tsx --input-type=module -e "const {seedRoutes}=await import('./apps/cms/src/cms/services/content.ts'); const {SITE_ID}=await import('./apps/cms/src/cms/contracts.ts'); const {pool}=await import('./apps/cms/src/cms/db/client.ts'); try { await seedRoutes(SITE_ID); } finally { await pool.end(); }"
```

Administrators and editors can manage registered editorial types through the existing policy. Authors and contributors currently have a narrower own-Post path; adding Recipes must not silently broaden their permissions. If those roles should manage the new collection, update both capability checks and SQL visibility filters with matching tests. Author profiles are a separate resource kind with their own ownership rules, not a content type added to this registry.

Sources: Archive route registration (repository: `apps/cms/src/cms/services/content.ts`); Capability rules (repository: `apps/cms/src/cms/policy/index.ts`); SQL visibility rules (repository: `apps/cms/src/cms/policy/query.ts`); Content-type and component extension contract (repository: `docs/developer-guide.md`).

## Treat stored contracts as long-lived data

Each content revision pins its exact definition version. Restoring history preserves that pin and does not rewrite the original snapshot. Component instances similarly pin immutable releases. Administrators build Components from supported blocks, expose typed inputs and publish a release. Inserting materializes defaults. Releasing v2 leaves v1 instances unchanged; a reviewed upgrade changes only a consuming draft, with undo and a separate publication decision. Image inputs use literal media selections because scalar fields are not typed image references.

For relational changes, update the Drizzle schema, generate SQL, review it and verify a fresh database plus an upgrade copy. Preserve custom migration constraints and runtime grants. For components that contain media or resource identifiers, extend reference extraction and authorized rendering as well as the visible editor. Otherwise publication, deletion protection and public media access cannot reliably understand the relationship. Run draft/save/publish/reload, invalid-input, role and public-rendering cases before considering the extension complete.

```powershell
npm run db:generate
npm run typecheck
npm run test:integration
node scripts/verify-extension.mjs
```

Archive hides a component from new insertion without changing pinned instances. Unpublishing or trashing a component with public consumers is rejected, and released revisions and media remain retained. The historical extension exercise predates the database registry; use the current content-builders integration suite for activation and release-pin behavior. Generate migrations only for relational changes and retain the exact version evidence in backups and restores.

Sources: Relational schema (repository: `apps/cms/src/cms/db/schema.ts`); Field, composition and reference validation (repository: `apps/cms/src/cms/validation.ts`); Content-type and component extension contract (repository: `docs/developer-guide.md`); Executed generic extension exercise (repository: `docs/evidence/extension-exercise.md`); [Drizzle schema declarations](https://orm.drizzle.team/docs/sql-schema-declaration).

## References

- Installed type and taxonomy registry — repository path: `apps/cms/src/cms/definitions/index.ts`

- TypeDefinition and supported scalar fields — repository path: `apps/cms/src/cms/contracts.ts`

- Content type builder and catalogue — repository path: `apps/cms/src/components/admin/type-builder.tsx`

- Content type retirement and permanent deletion — repository path: `apps/cms/src/cms/services/type-lifecycle.ts`

- Content-type and component extension contract — repository path: `docs/developer-guide.md`

- Field, composition and reference validation — repository path: `apps/cms/src/cms/validation.ts`

- Component rendering and fields — repository path: `apps/cms/src/cms/components/config.tsx`

- Archive route registration — repository path: `apps/cms/src/cms/services/content.ts`

- Capability rules — repository path: `apps/cms/src/cms/policy/index.ts`

- SQL visibility rules — repository path: `apps/cms/src/cms/policy/query.ts`

- Relational schema — repository path: `apps/cms/src/cms/db/schema.ts`

- Executed generic extension exercise — repository path: `docs/evidence/extension-exercise.md`

- [Drizzle schema declarations](https://orm.drizzle.team/docs/sql-schema-declaration). Schema and migration-generation background; CMS compatibility rules are application-specific.

## Related guides

- [How the CMS works](https://grocms.com/docs/how-cms-works)

- [Technology stack](https://grocms.com/docs/technology-stack)

- [Local setup](https://grocms.com/docs/local-setup)

- [Troubleshooting](https://grocms.com/docs/troubleshooting)
