Configuration
All configuration lives in cms.ts, which exports the cms instance your pages query and the handler serves. alinea init creates it in the project root, or in src/ when that folder exists. Alinea also finds it as cms.tsx, cms.js or cms.jsx; pass --config to the CLI to use another location.
import {Config, Field} from 'alinea'
import {createCMS} from 'alinea/next'
const Page = Config.document('Page', {
fields: {
body: Field.richText('Body')
}
})
export const cms = createCMS({
schema: {Page},
workspaces: {
main: Config.workspace('Main', {
source: 'content',
mediaDir: 'public/media',
roots: {
pages: Config.root('Pages', {contains: ['Page']}),
media: Config.media()
}
})
},
baseUrl: {
development: 'http://localhost:3000',
production: process.env.NEXT_PUBLIC_SITE_URL ?? 'https://example.com'
},
handlerUrl: '/api/cms',
adminPath: '/admin',
preview: true,
enableDrafts: true
})Import createCMS from alinea/next in a Next.js project. It accepts the options below and returns the cms object with the query methods (find, first, get, count), cms.previews and cms.workspaces.
Required options
schema
An object of all your types, keyed by name: {Page, BlogPost}. The key is the type name stored in content files and used in contains and _type, so don't rename it once content exists. See Schema.
workspaces
An object of workspaces, at least one. Keys may only use a-z, A-Z, 0-9 and _. With more than one workspace, each source folder must be named after its key and all of them must sit in the same parent folder.
handlerUrl
The URL of the API route that serves the dashboard and media files: the route that exports createHandler from alinea/next. alinea init sets it to /api/cms and creates app/(alinea)/api/cms/route.ts. A relative URL is resolved against baseUrl. Without it alinea dev, alinea build and your site throw Missing handlerUrl in Alinea config.
baseUrl
The URL of your website, either one string or one per environment: {development, production}, picked by NODE_ENV. A value without a protocol gets https://.
alinea buildexits withNo baseUrl was set for the production buildwhen there is no production URL.In production your site reaches the handler at
handlerUrlresolved against this URL, to check for new content. That includesnext starton your machine, which talks to the production URL.
Most projects read the production URL from an environment variable, see Environment variables below.
Dashboard options
adminPath
The path the dashboard is served on, defaults to /admin. During development withAlinea forwards it to the local dashboard of alinea dev; alinea build writes the dashboard to public/admin.html plus a public/admin/ folder of assets (named after the path), and withAlinea serves it from there. Media URLs live below it as well: /admin/file/.... It can't be /. It replaces the deprecated dashboardFile option of 1.x.
preview
Shows a preview next to the editor. true loads your site in an iframe and updates it while editors type, see Live previews. You can also pass a React component that receives {entry} and renders the preview itself. Workspaces, roots and types accept the same preview option, the most specific one wins: preview: false on a type hides the preview for entries without a page, such as site settings.
enableDrafts
Off by default: every save in the dashboard publishes right away. With enableDrafts: true:
Editors get a separate Save draft action next to Publish, and new entries start as drafts.
Published entries can be unpublished again.
The published version stays live until the draft is published.
The MCP server accepts
publish: falseto save drafts.
roles
Custom roles with permission policies, see Roles and permissions. An admin role is always added.
maxUploadSize
The maximum size of an uploaded file, in bytes, for example 20 * 1024 * 1024 for 20 MB. Larger uploads are refused with an error that names the limit. No limit by default.
resizeImages
Scale down JPEG, PNG and WebP uploads larger than maxWidth or maxHeight pixels before they are stored. Defaults to {maxWidth: 2560, maxHeight: 2560}, set it to false to store uploads as they are. The dashboard resizes in the browser, so large photos never travel to the server. quality sets the JPEG and WebP encoding quality between 0 and 1, 0.85 by default. When you upload with cms.upload, pass transformImage from alinea/core/media/TransformImage next to createPreview; it uses sharp on the server. The maxUploadSize limit applies to the resized image. Images keep their format; animated PNG and WebP images, GIFs and SVGs are stored as they are.
Content delivery options
syncInterval
How your deployed site keeps its content fresh. The site checks the content revision of the repository and syncs right away when it changed; syncInterval is the fallback interval in seconds when that revision can't be determined. Defaults to 60.
0syncs on every query.Infinitynever syncs: the site only serves the content it was built with, and new content goes live with the next deploy.
A single query can override it with syncInterval, or skip syncing with disableSync: true. See Instant publishing.
publicDir
The folder your site serves static files from, defaults to public. alinea build writes the dashboard into it, and media files stored inside it (the workspace mediaDir) are served from there.
tracer
A function to instrument Alinea operations, for example with OpenTelemetry spans. It is called as tracer(name, run) and must return the result of run(). Errors thrown by the tracer itself are ignored, the operation still runs.
import {trace} from '@opentelemetry/api'
const tracer = trace.getTracer('alinea')
export const cms = createCMS({
// ...
tracer(name, run) {
return tracer.startActiveSpan(name, async span => {
try {
return await run()
} finally {
span.end()
}
})
}
})Good to know
Environment variables
cms.ts runs on the server and in the dashboard, which runs in the browser. In the dashboard only variables that start with NEXT_PUBLIC_ or PUBLIC_ are defined; other process.env values are undefined there and log a warning. Use a public variable for anything the config needs, such as the production URL, and keep secrets in the handler route instead.
The CLI reads .env.local, or .env when there is no .env.local, from the project folder or the nearest parent folder that has one. Variables already set in the environment win.
Splitting up the config
Nothing requires one big file. Most projects keep each type in its own file and import them into cms.ts, for example schema/Page.ts or one folder per page type with its view component next to it, as the tutorial does. The config file itself only has to export cms as a named export, a default export is refused with No export named cms found.
Dealing with errors
Your config file is compiled and executed by alinea dev and alinea build. An error thrown while loading it points to the compiled file:
Error: Fail
at file:///home/alineacms/alinea/node_modules/@alinea/generated/config.js?1706175675574:419:7
at ModuleJob.run (node:internal/modules/esm/module_job:194:25)Alinea compiles your config with a source map. Enable Node's --enable-source-maps flag in your scripts to get positions in your own files:
{
"scripts": {
"dev": "NODE_OPTIONS=--enable-source-maps alinea dev -- next dev",
"build": "NODE_OPTIONS=--enable-source-maps alinea build -- next build"
}
}The error now points to the right file:
Error: Fail
at <anonymous> (/home/alinea/apps/dev/cms.ts:278:7)
at ModuleJob.run (node:internal/modules/esm/module_job:194:25)