⚠️ Work in progress — createCMS is pre-1.0 and not production-ready (not tested in production). Expect breaking changes.
createCMS

Templates

Default property values per collection, block, and property.

A template is a default value for a single block property, scoped to a collection and a block type. When a block is created, any optional property the caller leaves unset is seeded from its template. It is the per-property, editor-owned answer to "what should this field start as?"

How templates are keyed

A template is identified by the triple (collection, blockType, propertyKey), so there is at most one default per property. Creating a second for the same triple fails with TEMPLATE_KEY_EXISTS.

collection: posts
blockType:  quote
property:   cite
template:   "© {{companyName}}"

Defining that template is a single call:

await cms.api.templates.createTemplate({
  body: {
    collection: 'posts',
    blockType: 'quote',
    propertyKey: 'cite',
    template: '© {{companyName}}',
  },
});

A template is data, not code

A block definition can already declare a per-property defaultValue. So why a second layer of defaults? Because the two live in different places and answer to different owners:

  • defaultValue is code. It is written into the block definition and compiled with your app: one value for every tenant and language, changed only by editing the source and redeploying.
  • A template is content. It is a row an editor writes and edits at runtime through createTemplate / updateTemplate, it scopes per tenant and per language like the rest of your content, and its {{variables}} are tracked so changing a variable reaches every template that uses it.

Templates are the runtime, per-scope layer of defaults sitting above the compile-time one. When both exist for a property, the template wins.

Where a new block's values come from

createBlock fills each unset property by merging three sources, lowest priority first:

  1. the block definition's defaultValue (the compile-time base)
  2. the template for (collection, blockType, propertyKey) (runtime, scoped)
  3. the value the caller passed in properties (always wins)

A caller value overrides both defaults; a template overrides the schema default; the schema default fills whatever is left. Only optional properties reach this merge: a required property is validated on the request before defaults apply, so the caller must always send it.

So a quote block created without a cite inherits the template, while one that passes cite keeps its own value:

const { blockId } = await cms.api.posts.createBlock({
  body: {
    rootId: 'root_launch',
    branchId: 'br_main',
    parentBlockId: 'root_launch',
    type: 'quote',
    properties: { text: 'Ship early, ship often.' },
    // cite omitted, so it is seeded with the raw "© {{companyName}}"
  },
});

Applied on create, once

Seeding happens only when a fresh block is added, and only for the constraints above:

  • createBlock only. duplicateBlock makes a faithful copy (it must match the original, not re-default) and updateBlocks applies a client-authoritative tree, so neither re-applies templates.
  • Text properties only. A template targets a string or richText property. createTemplate rejects any other type, or a property that does not exist on the block, with TEMPLATE_PROPERTY_INVALID.

There is no client wiring to do: if a template exists for a property the caller left unset, it is used.

Stored raw, resolved on read

The raw template string is what gets stored, both in the template row and in the property it seeds. Nothing is frozen at creation. The seeded property still holds the literal © {{companyName}}, so it resolves at read time like any other content and tracks later changes to companyName (see variables, where read paths substitute {{key}} and the editor reads raw: true so the binding survives edits).

Prefilling an editor with getTemplateDefaults

Before any block exists, an editor opening the "add block" form wants to show the defaults a new block would receive, already resolved for display. That is what getTemplateDefaults returns: the resolved value for every templated property of a (collection, blockType) pair, empty when the pair has none.

const { defaults } = await cms.api.templates.getTemplateDefaults({
  query: { collection: 'posts', blockType: 'quote' },
});
// defaults → { cite: '© Acme Inc.' }   ({{companyName}} resolved for the form)

This is the one place the resolved form is handed back. createBlock seeds the raw form so the binding stays live, whereas the editor form only needs a value to preview. To resolve a single arbitrary string rather than a whole block type, use resolveTemplate.

Scoping (i18n / multi-tenant)

Templates scope per tenant and per language, just like content, so getTemplateDefaults and the seed applied on create both see only the active scope's rows. See i18n and multi-tenant.

For the CRUD methods and exact signatures, see the Templates reference.

On this page