Skip to main content
CMS

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:

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:

ts
// @/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'}),
  ],
});
ts
// @/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 from url. domain sets the domain for multi-domain sites.

  • preview.defaultImage and preview.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}.

  • autolock and autolockReason: 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.

1
2
3
4
5
6
7
8
9
10
11
12
Breakpoint: