Model reusable categories and authors, choose reference shapes, and verify related-record permissions and public reads in BTST CMS.

Use a CMS relationship when several records should refer to the same editable thing. Use an embedded field when its value belongs to one record and should change independently. That choice determines how editors update content and how your React app reads it.
Imagine a directory of developer tools. Each tool has a name, description, website, and categories. Copying the category label into every tool is convenient at first. Renaming that category later becomes a coordinated update across many records. A shared category record makes the rename one editorial operation.
Ask what should happen when an editor changes a value:
| Content | Useful representation | Reason |
|---|---|---|
| A tool's short description | Embedded string | It belongs to that tool |
| A reusable category | Related record | A rename should affect every reference |
| An author with a biography page | Related record | The author has an independent identity |
| A historical quote and attribution | Usually an embedded snapshot | Later profile edits should not rewrite the original attribution |
Relationships introduce more than a picker. They require rules for missing targets, deletion, permissions, and public rendering. An embedded snapshot avoids a lookup but also stops following future changes. Choose that behavior intentionally.
BTST CMS 3.0.0 uses Zod schemas for content types and relation metadata for its editor and backend. This example defines a reusable category and a resource with multiple category references:
import { z } from "zod";
export const CategorySchema = z.object({
name: z.string().min(1),
});
export const ResourceSchema = z.object({
name: z.string().min(1),
categories: z.array(z.object({ id: z.string() })).default([]).meta({
fieldType: "relation",
relation: {
type: "manyToMany",
targetType: "category",
displayField: "name",
creatable: false,
},
}),
});
Register both schemas as content types in your existing backend, using category as the target type's slug. The example deliberately requires categories to exist first; editors select them rather than creating a new spelling from every resource form. It is a schema fragment, not a complete stack configuration. The CMS installation and relationship guide covers registration and the generated UI.
The reference value stores an ID, not a copied category name. belongsTo represents one related item; array-based relations represent multiple references. The released relation types document the supported representations.
Saving a reference does not mean every response contains the complete related record. Decide whether a page needs IDs, category labels, or full category content, and use the appropriate read operation. BTST offers populated-item reads and relation-based filtering; consult the CMS docs for their response shapes rather than treating IDs as embedded objects.
A directory card may need only the resource title and category names. A category landing page may need a filtered list of resources. Avoid fetching the entire directory and filtering it in the browser merely because the editor exposes a convenient category picker.
An editor who can edit a resource is not automatically allowed to attach every category or create new categories. Similarly, access to one public record should not reveal a restricted target through population.
With server authorization configured, BTST's request-scoped CMS operations evaluate the relevant content-type and record permissions for related targets. This requires passing the server auth adapter to the backend stack; authorization is opt-in in 3.0.0. Keep visitor-facing reads on the request-scoped API and configure those rules in your application. Raw or trusted server APIs have a different trust boundary; they are not substitutes for checking a visitor's access. See authorization and the released CMS operations.
Create two resources that share one category. Rename the category and confirm both public views display the intended new name. Attach a nonexistent ID and confirm the backend refuses it. Try a category the current editor cannot read or use, and verify the populated response does not expose it.
Then test deletion on disposable records. Decide whether deleting a category should be blocked, remove references, or require an editorial replacement. Do not assume a relation label promises a particular cascade policy. Check the actual behavior and add application rules where necessary.
For an existing library of copied labels, prepare a reviewed label-to-ID mapping before rewriting records. Preserve a backup and verify counts and public pages after the migration. The CMS plugin overview helps assess the supplied workflow; the Blog-versus-CMS guide covers the earlier decision about whether a structured content model is needed at all.