Keep your content model in git
A content model that lives only in the editor is hard to review, hard to reproduce in staging, and
easy to change by accident. content-sdk-apply ships with @ebitex/content-sdk and lets you keep the
model in your repository instead, then apply it to any environment.
It reads and writes through the Content Management API. It never decides what changed: it asks the server, which compares your model with what the environment holds right now.
Set up
Section titled “Set up”Create a management key in Content, under Settings → Management keys. A key belongs to one
authoring environment, so the key you use is the environment you change. There is no environment
argument and no --key flag, because a flag lands the credential in shell history and CI logs.
export EBITEX_CONTENT_MANAGEMENT_KEY=…| Command | The key needs |
|---|---|
plan |
content.management.read |
apply |
read and content.management.write (plus publish for --publish) |
pull |
content.management.read |
Start from what you have
Section titled “Start from what you have”Pull the environment out as a model, then plan against it. Nothing should differ:
npx content-sdk-apply pull --model content-modelnpx content-sdk-apply plan --model content-modelpull writes one JSON file per item and a content.lock.json. Commit both.
Write a model
Section titled “Write a model”A model names things by the identity the server matches on: an external id, a folder path, a category key. It never contains a Guid, so the same files apply to staging and to production.
// Content as code: a content model kept in your repository and applied to an environment with// `content-sdk-apply`. A model names things by the identity the server matches on (an external id,// a folder path, a category key) and never by Guid, so the same file applies to staging and to// production. Save this as `content-model/index.ts` (or `.mjs`) and run://// EBITEX_CONTENT_MANAGEMENT_KEY=… npx content-sdk-apply plan --model content-model/index.mjs// EBITEX_CONTENT_MANAGEMENT_KEY=… npx content-sdk-apply apply --model content-model/index.mjsimport { defineModel, file, ref } from '@ebitex/content-sdk/model'
export default defineModel({ contracts: [ { externalId: 'hero-banner', name: 'Hero banner', fields: [ { externalId: 'heading', name: 'Heading', fieldType: 'shortText', isMandatory: true }, { externalId: 'image', name: 'Image', fieldType: 'blob' }, { externalId: 'next', name: 'Next banner', fieldType: 'shortText' }, ], }, ], folders: [{ path: 'Library/Heroes' }], components: [ { externalId: 'home-hero', name: 'Home hero', folder: 'Library/Heroes', contract: 'hero-banner', document: { heading: 'Welcome', // A local file: the CLI hashes it, asks the server whether it already holds the bytes, and // uploads it only when it does not. Paths are relative to the model. image: file('./assets/hero.png'), // The Guid of another item in this model, wherever a document needs one. next: ref('component', 'about-hero'), }, }, { externalId: 'about-hero', name: 'About hero', folder: 'Library/Heroes', contract: 'hero-banner', document: { heading: 'About us' }, }, ],})ref() stands in for the id of another item, and file() for a local file. The CLI hashes each file,
asks the server whether it already holds those bytes, and uploads only what is missing.
Plan, then apply
Section titled “Plan, then apply”npx content-sdk-apply plannpx content-sdk-apply applyplan needs only the read scope and writes nothing, so it makes a safe pull-request check. It exits
with a non-zero code when the plan has blockers. apply writes only the items that differ, and a
second run with no changes writes nothing.
When someone edited it in Content
Section titled “When someone edited it in Content”content.lock.json records the state of every item the last time you applied. That is how the server
tells your repository changed (update) from a person changed it too (conflict). apply refuses
a conflict and names every item; pass --confirm-overwrite only when your model should win.
Without the lockfile every difference is a conflict, which is why pull comes first.
Items you stopped declaring
Section titled “Items you stopped declaring”If an item you applied before is no longer in the model, plan lists it as an orphan. Nothing
removes it. Delete it in Content if it should go.
Run it in CI
Section titled “Run it in CI”Plan on pull requests with a read-only key, and apply on merge with a write key. Commit the lockfile back after an apply, or the next run will report conflicts.
- run: npx content-sdk-apply plan env: EBITEX_CONTENT_MANAGEMENT_KEY: ${{ secrets.CONTENT_STAGING_READ_KEY }}To target production, use a second job with a second key.
Realms
Section titled “Realms”A key is bound to one realm as well as one environment, so the key you use is also the realm you change: there is
no way to pick a realm per request. Pass --realm <key> to make a run stop unless the key acts in the realm you
expect. It checks, it does not select. The root realm’s key is root, which is what every key minted before realms
acts in.
npx content-sdk-apply plan --realm brand-aKeep one lockfile per realm, because a state token only means something to the realm that issued it. Name it for the
realm: content.lock.json for the root, content-brand-a.lock.json for any other, passed with --lock.
What it leaves alone
Section titled “What it leaves alone”It never deletes content, never merges two edits, and never moves content between environments. For the last, use promotion in Content, or a deployment key from a pipeline (Promote content from a pipeline).