# The model in Git (/docs/guides/model-in-git)

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](/docs/guides/content-model#the-model-file).

## Management tokens [#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](/docs/guides/roles): 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](/docs/api/management). The delivery API refuses it.

Keep it in your CI's secrets. Revoke it in Settings.

## The CLI [#the-cli]

```sh
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 nothing
```

`plan` 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](/docs/sdk/cli) for the options.

## Locking the model to code [#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 [#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`.

```yaml title=".github/workflows/content-model.yml"
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:

1. Change the model in the dashboard on develop, then `model pull` and commit the file; or edit the file directly.
2. Open a pull request: the plan shows what will change.
3. Merge: the workflow applies it. Applying sends `model.changed` [webhooks](/docs/guides/webhooks).
