Plan required fields, renamed properties, defaults, and stored records when a code-defined CMS content schema changes.

Adding a required field to a CMS schema can break the next edit of an old record. The editor now expects the field, but the stored content does not contain it. A successful deployment alone does not make that content valid under the new schema.
BTST's CMS defines content types in code with Zod. In BTST 3.0.2, the backend synchronizes those definitions into stored content-type metadata. Existing content items remain separate JSON records. Treat a schema change and a content migration as two related pieces of work. The CMS reference covers the content-type configuration.
The released CMS synchronization code finds content types by slug and updates their name, description, and JSON Schema. It does not iterate through every content item and rewrite its data.
The read serializer parses stored JSON into parsedData; that parsing does not validate every field against the current Zod schema. Passing a TypeScript generic to a CMS hook describes the shape your code expects. It does not transform an older record into that shape.
On update, the CMS merges submitted fields with the existing record data and validates the result against the current content-type schema. This means an editor changing only a title can encounter a validation error for a different, newly required field. Those behaviors are visible in the release's synchronization and operation code and its adjacent operations.ts and getters.ts files.
Suppose product content originally contains name and price. You want a summary. An optional field permits existing records to remain valid while editors fill it in:
import { z } from "zod";
export const ProductSchema = z.object({
name: z.string().min(1),
price: z.number().nonnegative(),
summary: z.string().min(1).optional(),
});
export type ProductData = z.infer<typeof ProductSchema>;
During this transition, the public renderer must handle a missing summary. Do not call string methods on it unconditionally or rely on a type assertion to conceal absent data. Once the content has been filled in and verified, a later release can make the field required.
Defaults need similar care. A default may affect validation when a value is absent; it is not proof that every stored row has been backfilled. Verify how your field's JSON Schema conversion and the CMS operation apply that default, then separately inspect the persisted result.
| Change | Work to consider |
|---|---|
| Add an optional field | Update rendering for absent values; fill content gradually |
| Add a required field | Backfill old records before requiring it |
| Rename a property | Preserve the old value and copy it deliberately to the new property |
| Change a field's type | Define conversion rules and record failures for review |
| Remove a field | Check templates, integrations, and rollback needs before discarding data |
Changing name to title in a schema is not an instruction to rename stored JSON keys. Use a transition where the application can read the old and new formats, then backfill through a controlled migration. Preserve record IDs, slugs, and relationships unless changing them is part of the intended migration.
A pure transformation can be checked before any write. This example produces a proposed value for one record; it does not write to the CMS:
import { z } from "zod";
const NewProduct = z.object({
title: z.string().min(1),
price: z.number().nonnegative(),
});
export function proposeProductRename(data: Record<string, unknown>) {
return NewProduct.safeParse({
title: data.title ?? data.name,
price: data.price,
});
}
Here an existing title takes precedence. An empty title or invalid price produces a failure for review. The proposed object includes only the fields declared above; expand the transformation to preserve every field your real content type needs. Do not write this small example over richer records unchanged.
Use a backup or a disposable copy of representative content. Include old records, already migrated records, missing values, and invalid values. Check that rerunning the migration produces the same valid result and that failures remain visible.
Deploy the compatible reader and schema before a backfill that depends on them. Avoid running application versions with conflicting content-type definitions against the same database during the transition: each can synchronize its own definition. After migration, test listing, detail rendering, and an ordinary editor update. Also test rollback with the new data shape.
Changes to a plugin's database tables still use the database migration workflow. Changes inside CMS JSON content need their own data plan. For the earlier modeling decision, see shared records versus embedded fields; for implementation, continue with the CMS documentation.