Guides · Modelling
The model in Git
Keep a site's content model next to its code as one JSON file, reviewed in pull requests and applied on merge, with management tokens, the CLI and a GitHub workflow.
A site's content model can live next to its code as one JSON file, reviewed in pull requests and moved from develop to production like the site itself. The file is the one Content model → Download model saves; its $schema gives editors validation and autocomplete. See the model file.
Management tokens
Create one under Settings → API tokens with the kind Management, once per environment the repository syncs (agents can create one with createApiToken, outside production). A management token:
- belongs to one space and one environment;
- acts as the person who created it, with their current role: it can change the model only while they are an admin or developer, and stops working when they leave the organization;
- only works on the management API. The delivery API refuses it.
Keep it in your CI's secrets. Revoke it in Settings.
The CLI
export CONTENT_MANAGEMENT_TOKEN=cms_m_…
npx @obelism/content model pull # write the environment's model to content/content-model.json
npx @obelism/content model plan # what applying the file would change, and what stands in the way
npx @obelism/content model apply # apply it in one transaction, or change nothingplan and apply exit with 1 when the file has problems (a content type without a description, a field whose type or localization changes, a content type in use changing kind, deleting a type other types use, and so on), listing each one; apply then changes nothing. In GitHub Actions they also write the plan to the job summary. See the CLI for the options.
Locking the model to code
Once an environment's model lives in Git, a change made in the dashboard would be undone, or deleted, by the next apply. Mark the environment's model as managed in code under Settings → Publishing → Where the content model is changed, or apply with --lock (npx @obelism/content model apply --lock, --unlock gives it back):
- the content model pages show every type read-only, with a note, and keep Export;
- the dashboard, the agent API and MCP can't create, change, duplicate, import or delete content types there (
409 model_managed_by_code); content model apply(the management API) and promoting the model from another environment still work, so code-managed models move through develop, staging and production as before;- content is never affected: editors keep editing.
It is set per environment: a common setup edits the model in the dashboard on develop and keeps staging and production locked to the file.
A workflow
This workflow plans on pull requests (against develop) and applies on merge: develop to the develop environment, main to production. A change that removes fields says how many entries lose values before you merge it. It needs two repository secrets, a management token per environment: CONTENT_TOKEN_DEVELOP and CONTENT_TOKEN_PRODUCTION.
name: Content model
on:
pull_request:
paths: [content/content-model.json]
push:
branches: [develop, main]
paths: [content/content-model.json]
jobs:
content-model:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/setup-node@v5
with:
node-version: 24
- name: Plan
if: github.event_name == 'pull_request'
run: npx --yes @obelism/content model plan
env:
CONTENT_MANAGEMENT_TOKEN: ${{ secrets.CONTENT_TOKEN_DEVELOP }}
- name: Apply
if: github.event_name == 'push'
run: npx --yes @obelism/content model apply
env:
CONTENT_MANAGEMENT_TOKEN: ${{ github.ref_name == 'main' && secrets.CONTENT_TOKEN_PRODUCTION || secrets.CONTENT_TOKEN_DEVELOP }}A typical loop:
- Change the model in the dashboard on develop, then
model pulland commit the file; or edit the file directly. - Open a pull request: the plan shows what will change.
- Merge: the workflow applies it. Applying sends
model.changedwebhooks.