When Migrations Are Needed
Not every schema change requires a migration. MDCMS distinguishes between non-breaking changes (handled automatically) and breaking changes (requiring explicit migration).| Change Type | Migration Required? | Reason |
|---|---|---|
| Adding a new optional field | No | Existing documents remain valid — the field defaults to undefined. |
| Adding a new content type | No | No existing documents are affected. mdcms schema sync registers the new type. |
| Adding a new environment | No | The environment starts empty. |
| Renaming a content type | Yes | Existing documents reference the old type name. |
| Removing a content type that has documents | Yes | Documents of that type must be migrated or deleted. |
| Changing a field’s type (e.g., string to number) | Yes | Existing frontmatter values are incompatible with the new type. |
| Renaming a field | Yes | Existing frontmatter uses the old field name. |
| Making an optional field required | Yes | Existing documents may have undefined for that field. |
| Removing a field | Yes | The field should be cleaned from existing frontmatter for consistency. |
Non-Breaking Changes
For non-breaking changes, the standard schema sync workflow handles everything automatically:Sync to the server
Run
mdcms schema sync. The server computes the new schema hash and updates
the schemaRegistryEntries and schemaSyncs tables.Database Migrations
Database schema changes (adding tables, columns, or indexes to the PostgreSQL schema) are managed through Drizzle ORM’s migration toolkit.Modify the schema definition
Edit
apps/server/src/lib/db/schema.ts to add, modify, or remove table
definitions.Generate the migration SQL
Run the Drizzle migration generator:
bash cd apps/server bun run db:generate This produces a timestamped SQL migration file in the
migrations directory.Review the generated SQL
Always review the generated migration before applying it. Drizzle generates
SQL based on the diff between the current schema definition and the last
known state. Verify that destructive operations (column drops, type changes)
are intentional.
Content Migrations
Content migrations transform existing documents when the schema changes in a way that is incompatible with stored data. Themdcms migrate CLI command handles this process.
Preview Mode
By default,mdcms migrate generates a migration plan without applying it:
- Which documents are affected
- What transformations will be applied
- Whether any documents cannot be automatically migrated
Apply Mode
To execute the migration, add the--apply flag:
- Transforms each affected document’s frontmatter according to the migration rules
- Updates the draft state (
body,frontmatter,updatedAt,draftRevision) - Auto-publishes updated documents that were previously in a published state
- Records the migration in the
migrationstable (name, schema type, documents affected, who applied it, when)
Breaking Change Scenarios
The following scenarios require explicit content migrations.Type Rename
When renaming a content type (e.g.,Post to BlogPost):
- Add the new type to
mdcms.config.ts - Run
mdcms schema syncto register the new type - Run
mdcms migrate --applyto update all documents from the old type to the new type - Remove the old type from the config
- Run
mdcms schema syncagain to deregister the old type
Field Type Change
When changing a field’s type (e.g.,tags: z.string() to tags: z.array(z.string())):
- Update the field definition in
mdcms.config.ts - Run
mdcms schema sync - Run
mdcms migrateto preview the transformation (e.g., wrapping string values in arrays) - Run
mdcms migrate --applyto execute
Field Removal
When removing a field from a content type:- Remove the field from
mdcms.config.ts - Run
mdcms schema sync - Run
mdcms migrate --applyto clean the removed field from existing document frontmatter
Field removal migrations are optional but recommended. Documents with extra
frontmatter fields will not cause errors, but removing stale data keeps the
content store clean and reduces confusion in API responses.
Migration Records
Every applied migration is recorded in themigrations table:
| Column | Description |
|---|---|
id | UUID primary key |
name | Human-readable migration name |
projectId | Target project |
environmentId | Target environment |
schemaType | Content type affected |
appliedAt | Timestamp of execution |
appliedBy | User who ran the migration |
documentsAffected | Number of documents transformed |