Noma

Blog

Headless CMS Schema Design Checklist for Fast Teams

March 21, 2026

Schema design quality determines how quickly teams can ship content-driven features. Bad models push complexity into every frontend branch; good models make APIs boring and editors confident.

Use this checklist before you lock a new collection or refactor an old one. Where it helps, notes refer to Noma (Noma Core: nomacms/nomacms-core) so your schema matches what the product actually enforces.

1) Field definitions

Intent: Every field has one job, one type, and clear validation—no “misc JSON” for things that should be structured.

  • Names are stable API keyscamelCase or snake_case per team convention; never rename casually (clients cache and query by name).
  • Types match reality — use number for numbers, enumeration for fixed vocabularies, richtext vs longtext on purpose (semantics and editors differ).
  • Required is explicit — in Noma, required on the field plus validations.required drive server-side rules in ContentRequest; don’t rely on UI hints alone.
  • Character limits — when you need them, charcount validations (min / max) align editor feedback with API rejection.
  • Groups and repeatablesgroup fields and repeatable options model lists and blocks without stuffing freeform HTML.

Noma field types to choose deliberately: text, longtext, richtext, slug, email, password, number, enumeration, boolean, color, date, time, media, relation, json, group (see schema in the dashboard and Field types in the app).

Editor vs API surface

  • hiddenInAPI — field stays in the admin but is omitted from ContentEntryResource (internal notes, migration-only fields).
  • hideInContentList — reduce noise in list tables without hiding from detail or API unless combined with hiddenInAPI.

2) Collections and entry shape

Intent: A collection is a bounded contract—one responsibility per collection unless you have a strong reason.

  • Singleton vs repeatingis_singleton enforces a single entry in the collection when creation rules are satisfied (with locale taken into account when provided—use for site settings, global config). Repeating models use normal collections with many entries.
  • Slug strategy — one collection often has a slug field tied to a source text field via slug.field options; decide whether slugs are readonly after publish.
  • Orderingorder on collections and fields controls admin UX; keep field order aligned with how editors think (hero → body → SEO).

3) Relations and references

Intent: Relations are the fastest way to create circular or N+1 API problems if you over-link.

  • Model only what you query — if the UI never needs “related author bios,” don’t add the relation until you need it.
  • Relation optionsrelation.collection and relation.type (cardinality / behavior) must match how the frontend resolves nested content (single vs multiple).
  • Avoid deep cycles — A → B → A makes caching and “what to include in one request” painful; prefer shallow graphs or denormalized snapshots for lists.
  • JSON fieldsjson is an escape hatch; use it for extension data, not for core layout that the site depends on.

4) Localization model

Intent: Locale rules are decided in the schema phase, not discovered at launch.

  • Project localesdefault_locale and locales[] define what editors can create; entries store locale per row.
  • Translation linkage — variants of the same logical item share translation_group_id; see How to Model Multilingual Content Without Duplicate Entries.
  • Slug per locale — slug fields are typically per entry; localized slugs need redirect and SEO planning.
  • Singleton + locale — one global settings row per locale is a common pattern; confirm whether the API should always pass locale or rely on default_locale for singleton reads.

5) Delivery API readiness

Intent: Frontend teams can query and filter without bespoke server code for every page.

  • Filterable fields — anything you sort or filter list pages by should be real fields (or core columns), not buried inside unstructured HTML.
  • Predictable payloads — align with ContentEntryResource shape: fields object keyed by field name; use exclude on list requests for large bodies when supported.
  • List semanticsstate, locale, sort, paginate / limit / offset, where — document what your app uses; see How to Design Stable Content APIs for Frontend Teams.
  • Preview vs production — draft content uses the same schema; state and preview tokens are app concerns, but the schema must allow draft entries without required fields that only exist at publish time (or use sensible defaults).

6) Publishing and governance

Intent: Editors know what “done” means; developers know what breaks the build.

  • Draft vs publishedstate and published_at are first-class on entries; required fields should apply to published content, not necessarily every draft (validate in your pipeline if the CMS allows saving incomplete drafts).
  • Roles and permissions — align collection usage with who can publish; sensitive collections should not share the same lax rules as marketing pages.
  • Change process — renaming or removing a field is a migration: plan for existing entries, API consumers, and static caches.

7) Performance and scale

Intent: Large rich text and huge media galleries do not surprise production.

  • List views — exclude heavy fields from list endpoints; load full entries only on detail routes.
  • Mediamedia fields and asset options (type, etc.) should match how assets are cropped and delivered (CDN, sizes).
  • Repeatable blocks — cap count in editor where possible; unbounded arrays hurt authoring and payloads.
  • Benchmark — run a realistic list query (page size, sort, filters) against production-like data.

Quick “go / no-go” before release

QuestionPass / fail
Can the frontend render every required field on every template that uses this collection?
Are list and detail queries defined (fields used, exclude if needed)?
Are locale and translation rules documented for this collection?
Are singleton rules understood (one entry vs one per locale, depending on how you create entries)?
Is there a migration plan for renames or type changes?

Apply this checklist to every new collection to avoid expensive refactors later.

Related:

Implementation reference (Noma Core)

  • Field modeltypes, required, validations, options (repeatable, hiddenInAPI, hideInContentList, relation, slug, media, …)
  • Content validationApp\Http\Requests\ContentRequest (required rules, char count)
  • API outputApp\Http\Resources\ContentEntryResource (respects hiddenInAPI)
  • Collectionsis_singleton, slug, fields tree