Skip to main content
This page covers every core concept in MDCMS. After reading it, you should be able to reason about how projects, environments, content types, documents, localization, references, and access control fit together.

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
All CLI commands, SDK queries, and Studio sessions operate within the scope of a single project. If you manage multiple sites or products, create a separate project for each.

Environments

An environment is an isolated content space within a project. Typical setups use production, 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
Key properties of environments:
  • Isolation — Each environment has its own documents. Editing content in staging does not affect production.
  • Schema overlays — Environments that extend another inherit the base schema and can add, modify, or omit fields. In the example above, staging extends production and adds featured and abTestVariant fields 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 in mdcms.config.ts using defineType() with Zod validators:
mdcms.config.ts
Each defineType() call accepts: The schema drives three things:
  1. Form generation — Studio renders appropriate input controls (text fields, date pickers, toggles, reference selectors) based on the Zod type of each field.
  2. Validation — Both the CLI and server validate frontmatter against the schema before accepting content.
  3. API behavior — The API exposes schema metadata so clients can introspect available types and their fields.
After modifying your types, sync the schema to the server:

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.
Because documentId is stable, you can safely reference documents by ID in your application code. The path is designed for human readability and filesystem mapping, but should not be treated as a permanent identifier.
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 with localized: true, each document can have independent locale variants.
Locale configuration is set globally in the config:
How localized documents work:
  • 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 locale parameter to query content in a specific language.
Non-localized types (where localized is false or omitted) operate in implicit single-locale mode. Their documents use the internal locale token __mdcms_default__ and do not participate in translation groups.

References

The fieldTypes.reference() helper creates a typed relationship between content types. A reference field stores the documentId of the target document.
How references are resolved:
  • At rest, reference fields store raw UUID strings (the target document’s documentId).
  • At query time, you can request resolution via the resolve parameter 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 null in the frontmatter and include an error entry in the resolveErrors object on the response.
Reference resolution error codes:
References can be optional (fieldTypes.reference("Author").optional()) or collected in arrays (z.array(fieldTypes.reference("BlogPost"))). The resolution engine handles both forms.

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

A typical production frontend key would use only 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/ in production”.
  • 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.

Architecture at a Glance

The following diagram shows how the main MDCMS components connect: