Projects
A project is the top-level tenant in MDCMS. Each project owns its own content, schema, environments, users, and API keys. Projects are identified by a slug (e.g.,marketing-site, developer-docs).
You set the project in your config file:
mdcms.config.ts
Environments
An environment is an isolated content space within a project. Typical setups useproduction, staging, and development, but you can define any set of environments.
Every project starts with a production environment. Additional environments are declared in mdcms.config.ts:
mdcms.config.ts
- Isolation — Each environment has its own documents. Editing content in
stagingdoes not affectproduction. - Schema overlays — Environments that
extendanother inherit the base schema and can add, modify, or omit fields. In the example above,stagingextendsproductionand addsfeaturedandabTestVariantfields via the.env("staging")sugar. - Clone & promote — You can clone all content from one environment to another, or promote a subset of documents between environments.
The
environment field in the config selects which environment the CLI
operates against by default. If omitted, operations target production.Content Types (Schema)
Content types define the shape of your content. They are declared inmdcms.config.ts using defineType() with Zod validators:
mdcms.config.ts
defineType() call accepts:
The schema drives three things:
- Form generation — Studio renders appropriate input controls (text fields, date pickers, toggles, reference selectors) based on the Zod type of each field.
- Validation — Both the CLI and server validate frontmatter against the schema before accepting content.
- API behavior — The API exposes schema metadata so clients can introspect available types and their fields.
Documents
A document is an instance of a content type. It consists of structured frontmatter (validated against the schema) and a Markdown or MDX body.
On disk, a document looks like a standard Markdown file with YAML frontmatter:
content/blog/hello-world.mdx
Draft/Publish Workflow
Every document follows a draft/publish lifecycle that tracks changes through immutable version snapshots.1
Create
A new document starts as a draft. It has no published version and is
only visible to users with
content:read:draft permission.2
Edit
Edits are auto-saved. Each save increments the
draftRevision counter.
Drafts do not create version history entries — they represent work in
progress.3
Publish
Publishing creates an immutable version snapshot. The
version number
increments and the snapshot captures the full frontmatter and body at that
point in time. You can attach an optional change summary to each published
version.4
Continue Editing
After publishing, further edits create new draft revisions on top of the
published version. The published content remains stable and visible to
readers.
5
Re-publish
Publishing again creates a new version snapshot. The full version history is
preserved, allowing you to review or compare any previous published state.
The
hasUnpublishedChanges flag on a document tells you whether the current
draft differs from the latest published version. This is useful for building
editorial dashboards that show pending changes.Localization
MDCMS supports content localization at the type level. When a content type is defined withlocalized: true, each document can have independent locale variants.
- Each locale variant is a separate document with its own
documentId,path,body,frontmatter, and version history. - Locale variants are linked by a shared
translationGroupId. This lets the Studio display a locale switcher and the API return all variants of a piece of content. - Each variant can be at a different stage in the draft/publish workflow. Publishing the English version does not affect the French draft.
- The SDK accepts a
localeparameter to query content in a specific language.
References
ThefieldTypes.reference() helper creates a typed relationship between content types. A reference field stores the documentId of the target document.
- At rest, reference fields store raw UUID strings (the target document’s
documentId). - At query time, you can request resolution via the
resolveparameter in the API or SDK. Resolution is shallow (one level deep) — it replaces the UUID with the target document’s data but does not recursively resolve references within the resolved document. - Unresolved references return
nullin the frontmatter and include an error entry in theresolveErrorsobject on the response.
API Keys
API keys provide scoped, token-based access to the MDCMS API. They are the primary authentication mechanism for server-to-server integrations and CI/CD pipelines. Each API key has:
API keys are prefixed with
mdcms_key_ for easy identification in logs and secret scanners.
Available Scopes
Full list of API key scopes
Full list of API key scopes
content:read and schema:read scopes, locked to the production environment via the context allowlist.
RBAC Roles
MDCMS uses role-based access control for user permissions. Roles are hierarchical — each role includes all permissions of the roles below it.Scope and Constraints
- Viewer and Editor roles can be scoped to a specific project, or even a folder prefix within a project/environment. This allows fine-grained access like “editor for
content/blog/inproduction”. - Admin and Owner roles are always instance-wide (global scope).
- Exactly one Owner must exist at all times. The system prevents removing or demoting the last Owner.