Skip to content

Roots

A root is a top-level section of a workspace, shown in the dashboard sidebar with its own tree of entries. Entries can have children, those children can have children, and so on. Use separate roots for content that's managed differently: pages, reusable settings, a collection of authors, media.

import {Config} from 'alinea'
import {Home, Settings} from './schema'

export const pages = Config.root('Pages', {
  contains: ['Page', 'Blog'],
  i18n: {locales: ['en', 'fr']},
  children: {
    index: Config.page({type: Home, fields: {title: 'Home'}})
  }
})

export const settings = Config.root('Settings', {
  contains: [],
  children: {
    settings: Config.page({type: Settings, fields: {title: 'Site settings'}})
  }
})

Options

Config.root(label, options) takes the label shown in the sidebar and:

  • contains: the types that can be created at the top level of the root. Entries below them follow the contains of their parent's type.

  • children: entries that always exist in this root, see "Seeded entries" below.

  • i18n: make the root translatable with {locales: ['en', 'fr']}. The first locale is the default in the dashboard.

  • overview: how the dashboard lists the top-level entries: columns, the default order (sort), the layout and actions, see below.

  • icon: a React component shown next to the root in the sidebar.

  • preview: turn live previews on or off for entries in this root, or pass a component that renders the preview. A type's own preview takes precedence.

  • view: a React component, or a path to one, that replaces the dashboard view of the root. It receives {root}. Use it to add a custom page to the dashboard.

  • openByDefault: open this root when the workspace is opened without a specific root. Otherwise the first root opens.

  • orderChildrenBy (deprecated): sort the top-level entries by one or more fields. Use overview.sort.

The key of a root (pages) is its name in content files and queries. It must start with a letter and contain only letters, digits and underscores. The first root of a workspace is the one the dashboard opens by default, unless another root sets openByDefault.

Overview

Opening a root lists its top-level entries. overview adds columns and sets the default order, which the sidebar tree follows too:

schema/authors.ts
import {Config, Field} from 'alinea'

export const Author = Config.document('Author', {
  fields: {
    avatar: Field.image('Avatar'),
    role: Field.text('Role')
  }
})

export const authors = Config.root('Authors', {
  contains: ['Author'],
  overview: {
    columns: {
      role: Config.column({header: 'Role', select: Author.role})
    },
    sort: {asc: Author.title},
    layout: 'cards'
  }
})

See Overviews for all options. Types configure the list of their children the same way.

Seeded entries

children creates entries from your config. Alinea adds them when it finds them missing, in every locale of the root, and editors can't move or delete them. That's the way to make sure a home page, a settings entry or a fixed overview page always exists:

import {Config} from 'alinea'
import {Blog, Home} from './schema'

export const pages = Config.root('Pages', {
  contains: ['Page'],
  children: {
    // The key is the path: "index" becomes the home page at /
    index: Config.page({type: Home, fields: {title: 'Home'}}),
    blog: Config.page({type: Blog, fields: {title: 'Blog'}})
  }
})

Config.page takes:

  • type (required): the type of the entry. It must be in your schema.

  • fields: default field values. They apply until an editor saves the entry. Without a title, the key is used.

  • children: seeded entries below this one, in the same form.

Removing a page from children doesn't delete its entry: it stays in your content and becomes a regular entry that editors can move and delete.

To query a seeded settings entry, filter by its type: cms.get({type: Settings}). If you created the entry by hand before seeding it, Alinea adopts the existing file with the same path.

Translations

With i18n, editors can translate every entry of the root into each locale, and each translation is a separate version with its own path and content. Urls start with the locale (/en/about, /fr/about), and content files are stored in a folder per locale (content/pages/en/about.json). Query a translation with the locale option, or all of them with Query.translations.

Leave i18n out of roots that aren't translated, such as a list of authors. See Internationalization for translating pages, per-field translations and dropping the locale from urls.

import {Config} from 'alinea'

export const pages = Config.root('Pages', {
  contains: ['Page'],
  i18n: {locales: ['en', 'fr', 'nl']}
})

Roots in queries

Roots are available on the CMS instance under their workspace, cms.workspaces.main.pages, and can be passed to the root or location query options:

import {cms} from '@/cms'
import {Query} from 'alinea'

const navigation = await cms.find({
  root: cms.workspaces.main.pages,
  locale: 'en',
  level: 0,
  select: {title: Query.title, url: Query.url}
})