Skip to content

Workspaces and roots

A workspace is a separate set of content with its own content directory and roots. Most sites need one. Add more to manage several websites, or clearly separate areas such as a website and an app, from one dashboard: editors switch between them in the sidebar.

cms.ts
import {Config} from 'alinea'
import {createCMS} from 'alinea/next'
import * as schema from './schema'

export const cms = createCMS({
  schema,
  workspaces: {
    main: Config.workspace('My site', {
      source: 'content',
      mediaDir: 'public/media',
      color: '#3F61E8',
      roots: {
        // Website pages
        pages: Config.root('Pages', {contains: ['Page', 'Blog']}),
        // Uploaded images and files
        media: Config.media()
      }
    })
  },
  handlerUrl: '/api/cms'
})

Options

Config.workspace(label, options) takes the label shown in the dashboard and:

  • source (required): the directory, relative to your project, where the workspace's content files are stored.

  • roots (required): the roots of the workspace, keyed by name. Add a media root to allow uploads.

  • mediaDir: the directory where uploaded files are stored, such as public/media. Keep it inside your public folder (public, or the publicDir you configured): media urls point to /admin/file/..., and the handler serves them from the public copy of the file.

  • mediaUrl: a prefix for the workspace's media urls, for example 'shop' gives /admin/file/shop/logo.png. Useful when several workspaces have files with the same name.

  • color: the accent color of the workspace in the dashboard. By default a color is derived from the label.

  • icon: a React component shown next to the workspace name.

  • preview: turn live previews on or off for everything in this workspace, or pass a component that renders the preview.

The key of a workspace (main) is its name in content and queries. Like type names, it has to start with a letter and contain only letters, digits and underscores.

Don't change the mediaDir of a workspace that already has uploads: media entries store the location of their file relative to it.

Several workspaces

With more than one workspace, every source directory must be named after its workspace key, and they must all be in the same parent directory:

cms.ts
import {Config} from 'alinea'
import {createCMS} from 'alinea/next'
import * as schema from './schema'

export const cms = createCMS({
  schema,
  workspaces: {
    main: Config.workspace('Website', {
      source: 'content/main',
      mediaDir: 'public/media/main',
      roots: {
        pages: Config.root('Pages'),
        media: Config.media()
      }
    }),
    docs: Config.workspace('Documentation', {
      source: 'content/docs',
      mediaDir: 'public/media/docs',
      roots: {
        pages: Config.root('Pages'),
        media: Config.media()
      }
    })
  },
  handlerUrl: '/api/cms'
})

All workspaces share the schema. Limit which types can be created where with the contains option of each root, and which editors can access a workspace with roles.

Entry urls don't include the workspace, so give each workspace its own routes, or use entryUrl to add a prefix.

Querying a workspace

Workspaces and roots are available on your CMS instance, so you can scope queries without repeating names:

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

const pages = await cms.find({
  workspace: cms.workspaces.main,
  select: {title: Query.title, url: Query.url}
})

Pass a root such as cms.workspaces.main.pages to root or location to narrow it down further, see Querying.

In this section