Schemas
Overview
CMS content models are defined in code, in .schema.ts files. They're versioned with the rest of your site, type-checked by TypeScript, and used to generate types for your templates with root-cms generate-types.
Collections
A collection is a group of docs that share a schema, like blog posts or landing pages. To add one, create collections/<Name>.schema.ts:
// @/collections/BlogPosts.schema.ts
import {schema} from '@blinkk/root-cms';
export default schema.collection({
name: 'BlogPosts',
description: 'Posts for the blog.',
url: '/blog/[slug]',
preview: {
title: 'title',
image: 'image',
},
fields: [
schema.string({
id: 'title',
label: 'Title',
translate: true,
}),
schema.image({
id: 'image',
label: 'Image',
help: 'Upload a 1600x900 JPG.',
}),
schema.richtext({
id: 'content',
label: 'Content',
translate: true,
}),
],
});
The url tells the CMS where each doc is served, for live preview and links.
Reusable schemas
Use schema.define() for content models that are used in more than one place, like page modules. schema.glob() collects every schema that matches a pattern, so adding a template file adds it to the CMS:
// @/templates/TemplateHero/TemplateHero.schema.ts
import {schema} from '@blinkk/root-cms';
export default schema.define({
name: 'TemplateHero',
fields: [
schema.string({id: 'title', translate: true}),
schema.richtext({id: 'body', translate: true}),
schema.image({id: 'image'}),
],
});
// @/collections/Pages.schema.ts
import {schema} from '@blinkk/root-cms';
export default schema.collection({
name: 'Pages',
url: '/[...slug]',
fields: [
schema.array({
id: 'modules',
label: 'Modules',
of: schema.oneOf({
types: schema.glob('/templates/*/*.schema.ts'),
}),
}),
],
});
Each schema.define() name must be unique across the project.
Collection options
Besides name, url, preview and fields, a collection can set how its docs are listed, named and published in the CMS:
-
group: groups collections together in the CMS's content list. -
previewUrl: the URL the editor's preview pane loads, if it's different fromurl.domainsets the domain for multi-domain sites. -
preview.defaultImageandpreview.fit: a fallback image for docs without one, and whether images are cropped ('cover', the default) or not ('contain'). -
slugRegex: a regular expression (as a string) that new slugs must match. -
autoSlug: pre-fills the slug of new docs from a template, e.g.'{date:YYYYMMDD}-{adjective}-{noun}'. Tokens are{date},{date:FORMAT},{adjective},{noun}and{random:N}. -
autolockandautolockReason: lock publishing whenever a doc is edited, so a change has to be unlocked deliberately before it goes live. -
sortOptions: extra sort options for the doc list, e.g.{id: 'title', label: 'Title', field: 'fields.meta.title'}. The first use of a new sort may return an error with a link to create the Firestore index it needs. -
customSorting: lets editors drag docs into a custom order (see below). -
viewOptions.compact: always lists docs in the compact table view, for collections without useful preview images or titles, like redirects. -
publishing.checks: checks that run before docs are published (see Checks).
With customSorting: true, the doc list has a Custom order sort where editors drag docs into place. The order is saved at sys.sortKey on the draft, so like any edit it goes live when the moved docs are published, and listDocs() returns docs in that order by default. Docs created outside the CMS have no position until an editor assigns one from the banner on the doc list; scripts can assign them with generateKeyBetween() and generateNKeysBetween() from @blinkk/root-cms.
Field types
Fields include string, richtext, image, file, select, multiselect, boolean, number, date, datetime, reference, object, array and oneOf. For every field and option, see schema in the API reference.
Available as of version 4.2.1. To cap how many items an array holds, like the modules on a page, set maxItems. Once it's reached, the CMS stops editors from adding, duplicating or pasting items, and writes from the API or Root AI that go over the limit fail validation.
Conditional fields
Available in version 4.1.0 and later.
Use showWhen to show a field in the editor only when a sibling field has a certain value, like a CTA label that only appears when a "Show CTA" checkbox is checked. field is the ID of a field in the same object, and dot-paths read nested values, e.g. 'media._type' for the type picked in a oneOf field.
// @/templates/TemplateHero/TemplateHero.schema.ts
import {schema} from '@blinkk/root-cms';
export default schema.define({
name: 'TemplateHero',
fields: [
schema.boolean({id: 'showCta', checkboxLabel: 'Show CTA'}),
schema.string({
id: 'ctaLabel',
// Shown when the "Show CTA" checkbox is checked.
showWhen: {field: 'showCta'},
}),
schema.oneOf({
id: 'media',
types: [ImageSchema, VideoSchema],
}),
schema.boolean({
id: 'autoplay',
checkboxLabel: 'Autoplay',
// Shown when a Video is picked for "media".
showWhen: {field: 'media._type', equals: 'Video'},
}),
],
});
A condition can set one of these operators:
-
None: the sibling value is truthy, e.g. a checked
boolean. -
equalsandnotEquals: the sibling value is (or isn't) exactly this value. -
inandnotIn: the sibling value is (or isn't) one of a list of values, e.g.{field: 'layout', in: ['split', 'stacked']}. -
exists: withtrue, the sibling has a value; withfalse, it's empty (unset,''or an empty array).
Pass an array of conditions to require all of them, e.g. showWhen: [{field: 'showCta'}, {field: 'variant', equals: 'button'}].
showWhen only changes what the editor shows. Hiding a field doesn't clear its data, so an editor can uncheck a box and check it again without losing what they entered, and the value is still published and returned by the API. Check the same condition when you render the field, e.g. {fields.showCta && fields.ctaLabel}.