@betttercms/codegen
v0.5.0
Published
Generate TypeScript types from your BetterCMS content schema — the single source of truth shared by the dashboard builder and the MCP tools.
Downloads
579
Readme
@betttercms/codegen
Generate TypeScript types from your BetterCMS content schema.
Your models are defined once — in the dashboard schema builder or by your AI through the MCP tools. Both write the same underlying field schema, so the types this generates can never drift from what your editors and agents actually create. This is the deterministic floor under the AI integration: even if an agent mis-wires something, you still have exact, committed types to fall back on.
Use it
# In your site repo (key from the dashboard → Settings → API Keys, content:manage scope)
BETTERCMS_API_KEY=bcms_xxx npx @betttercms/codegen --out src/bettercms.generated.ts✓ Generated 2 model types → src/bettercms.generated.tsCommit the generated file and re-run codegen after any schema change (a CI step or the BetterCMS GitHub Action does this for you on every build).
Options
| Flag | Env | Default |
|------|-----|---------|
| --out, -o | — | bettercms.generated.ts |
| --bindings-out | — | (off) — also emit Live Preview bindings to this path |
| --api-url | BETTERCMS_API_URL | https://api.bettercms.ai/api/v1 |
| --key | BETTERCMS_API_KEY | — |
What it emits
For a blog model with title (text, required), body (richtext), hero (group):
export interface BlogFields {
readonly title: string;
readonly body?: RichText;
readonly hero?: {
readonly heading: string;
};
}
export interface BetterCMSSchema {
readonly "blog": BlogFields;
}
export type BetterCMSModelSlug = keyof BetterCMSSchema;The BetterCMSSchema registry lets the Next.js adapter type getEntry("blog", …) by slug.
Field type → TypeScript
| Field | TS |
|-------|-----|
| text · date · datetime | string |
| richtext | RichText |
| image | BetterCMSImage |
| boolean / number | boolean / number |
| select | "a" \| "b" (or string) |
| reference / multi-reference | string / string[] (entry ids) |
| array | string[] / number[] |
| group | { …nested } |
| repeater | Array<{ …nested }> |
Live Preview bindings (--bindings-out)
Pass --bindings-out src/bettercms.bindings.generated.ts to also emit a tiny, schema-derived
helper that powers the dashboard's Live Preview editor. Spread a binding onto the element
that renders each field — the editor reads the resulting data-bcms-field attribute to make the
real, running site editable in place:
import { bcms } from "./bettercms.bindings.generated";
<h1 {...bcms.blog.title}>{entry.fields.title}</h1> // scalar
<li {...bcms.blog.tags.value(i)}>{tag}</li> // primitive-array item
<article {...bcms.blog.features.$(i)}> // array item root
<h3 {...bcms.blog.features.label(i)}>{f.label}</h3> // array item sub-field
</article>The attributes are emitted only when the site is built with BCMS_ANNOTATE set (preview
builds); a normal production build ships zero extra attributes (bcmsField returns {}). Same
generated file for both — no separate mode. Bindings follow the editor's one-level path grammar
(field, field[i], field[i].sub); non-repeatable zones and deeper nesting aren't
index-addressable yet, so they're omitted rather than emitted as paths that can't bind.
Library API
import { generateTypes, generateBindings, fetchModels } from "@betttercms/codegen";
const models = await fetchModels({ apiUrl, apiKey });
const types = generateTypes(models); // pure, deterministic
const bindings = generateBindings(models); // pure, deterministic (Live Preview)generateTypes and generateBindings are pure and deterministic — same models in, byte-identical output out.
