Reference › Config
Introduction
Content
Using content
Plugins
Operations
Recipes
Config reference
Every option of easy-cms.config.ts, grouped by where it goes. For explanations and examples,
follow the links to the guide. A test checks this page against the types in @easy-cms/core,
so every option is listed here.
Easy CMS checks the config when it starts. An option that was renamed is an error that names the
new one (e.g. admin.siteUrl is now `siteURL` ), so an upgrade can't quietly drop a setting. An
option Easy CMS doesn't know is logged as a warning with the closest name (did you mean `maxLength`?).
import { defineConfig } from '@easy-cms/core'
export default defineConfig({ secret, db, collections, /* … */ })Top level
| Option | Type | Default | |
|---|---|---|---|
secret | string | — | Required, 32+ characters. Signs sessions and preview tokens. |
rateLimit | object | — | Requests per minute per IP, user and API key: { window, anonymous, users, apiKeys }, {} for the defaults (rate limits). |
previousSecrets | string[] | — | Secrets used before secret: what they signed still verifies (rotating the secret). |
db | DatabaseAdapter | — | Required. sqlite() or postgres(). Databases |
serverURL | string | — | Public origin (https://example.com); makes media URLs absolute. |
cors | string[] | '*' | [] | Origins whose browser code may call the API. Security |
cronSecret | string | CRON_SECRET | Bearer secret for GET <api>/jobs/run, at least 32 characters (a shorter CRON_SECRET is a warning). Scheduling |
webhooks | WebhookConfig[] | [] | See webhooks. |
events | string[] | [] | Events of the app or its plugins besides content changes (<area>.<what>, e.g. order.paid), sent with cms.emit() to webhooks that list them. Webhooks |
jobs | JobConfig[] | [] | Work run with the scheduled jobs, e.g. a plugin's clean-up. See jobs. |
timeZone | string | the server's | IANA time zone of times of day in the config (jobs[].at, backups.at), e.g. Asia/Bangkok. The admin shows and takes every date in it, whatever the browser's zone. The admin |
localization | LocalizationConfig | false | — | See localization. |
routes | RoutesConfig | See routes. | |
admin | AdminConfig | See admin. | |
upload | UploadConfig | See upload. | |
auth | AuthConfig | See auth. | |
collections | CollectionConfig[] | [] | See collections. |
globals | GlobalConfig[] | [] | See globals. |
endpoints | Endpoint[] | [] | See endpoints. |
cliCommands | CliCommand[] | [] | easy-cms <name> commands, e.g. from plugins: { name, description, help?, run({ cms, args, flags, log }) }. CLI |
onRequest | OnRequest | OnRequest[]: ({ headers, url, user, cms }) => { context?, user? } | — | Runs on each API request once its user is known: returns the request's context (e.g. its tenant) and may change the user (their role there, scoped). Usually set by a plugin. A list runs in order: each gets the user the one before returned, and their context objects are merged. Multi-tenant |
apiKeys | boolean | false | API keys under Settings, for scripts and other apps. API keys |
email | EmailAdapter | — | Sends email for plugins, e.g. smtp() or consoleEmail(). Email |
backups | BackupsConfig | by hand only | Database backups on a schedule. See backups. |
audit | boolean | AuditConfig | off | The audit log: who changed what, sign-ins, admin actions. See audit. |
countCacheSeconds | number | 5 | Seconds a list's total (totalDocs, totalPages) is reused for the same query; the documents are always current. 0: count every time. Counting |
maxPage | number | no limit | The deepest page the REST API serves to API clients (the signed-in admin keeps its pages); deeper requests get 400, pointing to cursor. Cursors |
httpCache | HttpCacheConfig | off (no-store) | Lets a CDN cache public reads of published content, with cache tags to purge by. See httpCache. |
outbox | boolean | false | Runs after hooks, webhooks and httpCache.purge from an outbox written in the save's transaction, after the response, retried until they succeed. Hooks must be safe to run more than once. Outbox |
telemetry | boolean | { traces?, metrics? } | off | OpenTelemetry spans and metrics through @opentelemetry/api, sent wherever the app's SDK sends them. Observability |
search | SearchAdapter | — | Relevance-ranked search of the collections that have search, e.g. memorySearch(). Search |
logs | LogsConfig | readable lines | { format: 'json' } for one JSON object per line, with request ids. See logs. |
plugins | Plugin[] | [] | (config) => config, run in order before validation; definePlugin(fn, { name, version, apiVersion? }) names one for the dashboard; apiVersion (PLUGIN_API_VERSION) stops startup when the plugin is for another plugin API. Plugins |
fieldTypes | FieldTypeDefinition[] | [] | Field types from packages, e.g. color from @easy-cms/fields. Custom field types |
routes
| Option | Type | Default | |
|---|---|---|---|
api | string | /api/cms | Where the REST API is served. |
admin
| Option | Type | Default | |
|---|---|---|---|
path | string | /admin | Where the admin is served. |
locale | 'en' | 'th' | en | Default admin language before a user picks one. |
brand | AdminBrand | {} | See brand. |
siteURL | string | / (Nuxt, Next.js) | The public site for "View site": a path or an http(s) URL. |
nav | NavGroup[] | — | The menu's groups: { id, label, icon?, order?, children? }, children one level deep. Built in: content, settings (site, people, system). The admin |
commands | { label, href, icon?, keywords? }[] | — | More entries of the command palette (⌘K) that open a page of the admin. |
modules | string[] | [] | Admin modules with Web Components: package exports or paths. Admin components |
pages | AdminPage[] | [] | Pages of their own at <admin>/p/<path>, e.g. from plugins. See pages. |
dashboard | DashboardWidget[] | [] | Panels on the dashboard after the built-in ones. See dashboard. |
switcher | { cookie, label, options } | false | — | A choice at the top of the menu for the whole admin, e.g. the tenant, kept in a cookie. options: a path under the API returning { options: [{ value, label }], all? }. Usually set by a plugin. |
brand
| Option | Type | |
|---|---|---|
name | string | Shown in the menu, login page and browser tab. Default "Easy CMS". |
logo | string | A path on your site or an https:// URL. |
color | string | Main color as #rrggbb; shades are derived. |
pages
| Option | Type | |
|---|---|---|
path | string | Required. Lowercase letters, digits and -, unique: the page is at <admin>/p/<path>. |
component | AdminComponent | Required. The page's body; the admin draws the header. |
label | string | { en, th } | Required. Title in the header, the menu and the browser tab. |
icon | AdminIcon | Menu icon. Default file-text. |
group | 'content' | 'settings' | false | string | Its menu group: content, settings, a group's id (admin.nav) or a label; false: not listed. Default content. |
order | number | Its place in its menu group: lower first. Those without one come after, in config order. |
access | ({ user }) => boolean | Who may open it, checked on the server. Default: every logged-in user. |
dashboard
| Option | Type | |
|---|---|---|
id | string | What roles are given in Settings → Roles (widget:<id>), so a panel keeps them when its tag changes. Default: the component's tag. |
component | AdminComponent | Required. The panel's content. |
width | 'half' | 'full' | half (default): the side column; full: below both columns. One column on phones. |
label | Label | Its name in Settings → Roles (auth.rbac). Default: the component's tag. |
access | ({ user }) => boolean | Who sees it, checked on the server. Default: every logged-in user. |
Pages and dashboard panels shows how to write them.
backups
| Option | Type | Default | |
|---|---|---|---|
frequency | 'daily' | 'weekly' | — | Back up automatically. Without it, admins back up by hand. |
at | string | 03:00 | When, as HH:MM in the server's time zone. |
keep | number | 7 | Finished backups to keep; older ones are deleted. |
dir | string | backups | Folder for the default local storage. Never served publicly. |
storage | StorageAdapter | local disk | E.g. s3Storage() with a private bucket. Public URLs log a warning. |
encryptionKey | string | — | Encrypts the backup files (AES-256-GCM). At least 32 characters, e.g. process.env.EASY_CMS_BACKUP_KEY. Backups |
sqlite | (options) => DatabaseAdapter | — | Postgres only: sqlite from @easy-cms/db-sqlite, which writes the backup file. Backups |
audit
| Option | Type | Default | |
|---|---|---|---|
keepDays | number | 365 | Days to keep entries; older ones are deleted. 0: keep them all. |
values | boolean | true | Keep values before and after a change; false: only which fields changed. |
failedLogins | number | 20 | Failed sign-ins within an hour that the dashboard warns about. Audit log |
scope | ({ context, user }) => string | null | — | The part of the site an entry belongs to, e.g. its tenant (usually set by a plugin). Admins of a part (scoped) see its entries; others the chosen part's, or all. |
httpCache
| Option | Type | Default | |
|---|---|---|---|
sMaxAge | number | 60 | Seconds a CDN serves a public read without asking again (s-maxage). |
staleWhileRevalidate | number | 300 | Seconds after that it may still serve it while it fetches a fresh one. |
purge | (tags) => void | Promise<void> | — | Called after public content changes, with its cache tags. Not awaited; a failure is logged. |
logs
| Option | Type | Default | |
|---|---|---|---|
format | 'pretty' | 'json' | 'pretty' | json: one object per line (time, level, message, requestId, error). EASY_CMS_LOG_FORMAT wins over it. Logs |
requests | boolean | true | With json, a line for every API request: method, path, status, duration, user. |
auth
| Option | Type | Default | |
|---|---|---|---|
roles | string[] | ['admin', 'editor'] | Must include admin. With rbac, the roles that always exist. Users & auth |
rbac | boolean | false | Roles and their permissions from the admin (Settings → Roles), on top of access rules. Roles |
providers | AuthProvider[] | [] | Signing in to the admin with outside accounts, e.g. [google()] from @easy-cms/auth-oauth. Single sign-on |
providerSignUp | { domains, role? } | — | With providers: people from these email domains get an account on their first sign-in, with role (not admin). |
password | boolean | { breakGlass } | true | false: only admins sign in with a password; { breakGlass: [emails] }: only those accounts (single sign-on for everyone). Everyone else uses providers. |
setupCode | string | EASY_CMS_SETUP_CODE | The code the first admin must enter on /admin, so nobody else claims a fresh site. Without one, no code is asked, and in production the server warns while no admin exists. One-click deploy |
tokenExpiration | number | 604800 (7 days) | Session lifetime in seconds. |
maxLoginAttempts | number | 5 | Failed logins allowed per email (and IP) within lockWindow. |
lockWindow | number | 900 (15 minutes) | In seconds. |
trustedOrigins | string[] | [] | Other origins that may send cookie-authenticated requests. |
resetPasswordExpiration | number | 3600 | Seconds a "forgot password" link works. Links need email, and serverURL in production. Forgotten passwords |
inviteExpiration | number | 604800 | Seconds an invitation link works. |
emails | { resetPassword?, invite?, passwordChanged? } | — | Functions ({ user, url, locale, expiresAt }) => { subject, text, html? } for your own email text. |
members | MembersConfig | — | People who sign in on the site, not the admin, e.g. customers. See members. |
members
| Option | Type | |
|---|---|---|
roles | string[] | Their roles, also in roles. Members never get into the admin, and isStaff (every collection's default) doesn't count them. Site members |
signUp | MembersSignup | Visitors create their own account: POST <api>/auth/signup. |
pages | { verifyEmail?, resetPassword? } | The site's pages that open members' email links with ?token=: paths on admin.siteURL (else serverURL) or URLs. Default: the admin's pages. |
emails | { verifyEmail? } | Your own text for the email that confirms an address. |
signUp
| Option | Type | Default | |
|---|---|---|---|
role | string | — | The role new accounts get: one of members.roles. |
verifyEmail | boolean | true | New accounts confirm their email with a link before signing in. |
turnstile | { siteKey, secretKey } | — | Cloudflare Turnstile, checked on every sign-up. |
upload
| Option | Type | Default | |
|---|---|---|---|
dir | string | uploads | Folder for the default local storage, from the project root. |
maxFileSize | number | 10485760 (10 MB) | In bytes. |
mimeTypes | string[] | ['image/*', 'application/pdf'] | Allowed types, detected from file contents: MIME types, type/*, or documents, office, archives. See File types. |
storage | StorageAdapter | local disk | E.g. s3Storage() from @easy-cms/storage-s3. Uploads |
imageSizes | ImageSize[] | [] | Resized copies (needs sharp). See image sizes. |
fromURL | UploadFromURLConfig | off | Uploads from links. See fromURL. |
privateStorage | StorageAdapter | storage without public URLs | Where files in private folders go. See Private folders. |
folders | boolean | false | Folders in the media library, and with auth.rbac who can use each. See Folders. |
fromURL
| Option | Type | |
|---|---|---|
allowedHosts | string[] | Required. images.example.com, *.example.com (subdomains) or * (any public host). |
allowPrivate | boolean | Also private network addresses (localhost, 10.x…). Default false. Read first |
Image sizes
| Option | Type | Default | |
|---|---|---|---|
name | string | — | Key in media.sizes, e.g. thumbnail. |
width | number | — | Width in pixels. |
height | number | — | Optional height. |
fit | 'cover' | 'contain' | 'inside' | cover | How to fit when both width and height are set. |
localization
| Option | Type | Default | |
|---|---|---|---|
locales | string[] | — | Content locales, e.g. ['th', 'en']. Localization |
defaultLocale | string | the first locale | Used when no locale is given; its values fill empty ones. |
fallback | boolean | true | Reads return the default locale's value when a value is empty. |
webhooks
| Option | Type | Default | |
|---|---|---|---|
url | string | — | Where to POST events. Webhooks |
events | string[] | all | create, update, delete, publish, unpublish, draft. |
collections | string[] | all but users | Collections to send events for; [] for none. List users to get user accounts. |
globals | string[] | all | Globals to send events for; [] for none. |
secret | string | — | Signs each attempt: x-easy-cms-timestamp and x-easy-cms-signature-v2 (checking it). |
headers | Record<string, string> | — | Extra request headers. |
events also takes the config's own events (e.g. order.paid): those are sent only to webhooks
that list them.
jobs
| Option | Type | |
|---|---|---|
name | string | Unique, lowercase with - and :, e.g. shop:carts. |
every | number | At most this often, in seconds; when each last ran is kept in the database. With at, whole days (default one). Default: every run. |
at | string | A time of day, HH:MM in timeZone. A missed time runs once when jobs next run. |
adminSchedule | boolean | Admins may change every and at on Settings → Jobs. Default false. |
run | ({ cms, now }) => unknown | The work. A failure is logged; the other jobs still run. |
They run with the scheduled jobs: every minute in a server, or each time a cron calls
GET <api>/jobs/run. Each run is claimed in the database, so it happens once across instances.
Admins see, run and pause them on Settings → Jobs. Jobs
collections
| Option | Type | Default | |
|---|---|---|---|
slug | string | — | Required. URL and table name: lowercase letters, digits, -, _. |
fields | Field[] | — | Required. See the field reference. |
labels | { singular?, plural? } | from the slug | Each a string or { en, th }. |
useAsTitle | string | — | Top-level field shown as the document title. |
drafts | boolean | false | Adds status (draft | published). Drafts |
versions | boolean | VersionsConfig | false | Keep a version of every save. See versions. |
schedule | boolean | false | Publish and unpublish at a set time (needs drafts). |
httpCache | false | { sMaxAge?, staleWhileRevalidate? } | the config's | This collection's httpCache times, or false to keep its reads no-store. |
search | { fields: string[] } | — | Indexes published documents for the config's search adapter: the fields to index, most important first. Search |
preview | ({ doc, locale }) => string | null | — | The page showing a document, for live preview. |
access | { read?, create?, update?, delete? } | logged in | Access control |
hooks | CollectionHooks | — | See hooks. |
admin | CollectionAdmin | — | See admin components. |
versions
| Option | Type | Default | |
|---|---|---|---|
keep | number | 50 | Versions kept per document; older ones are deleted. |
hooks
Each is a list of functions. Hooks
| Hook | Arguments | Return |
|---|---|---|
beforeValidate | data, operation, previousDoc? | new data, or nothing |
beforeChange | data, operation, previousDoc? | new data, or nothing |
afterChange | doc, operation, previousDoc? | — |
beforeDelete | id | — |
afterDelete | id, doc | — |
afterRead | doc | new doc, or nothing |
Every hook also gets user, cms and slug.
Admin components
| Option | Type | |
|---|---|---|
icon | AdminIcon | Menu icon. Default file-text. Branding |
order | number | Its place in its menu group: lower first. Those without one come after, in config order. |
editIn | 'page' | 'drawer' | drawer edits in a panel over the list (without drafts, versions or preview). Default page. |
sidebar | SidebarPanel[] | Panels in the edit page's side column; { tag, props, position: 'top' } puts one above the rest. Admin components |
group | string | Label | false | Its menu group: a group's id (admin.nav, e.g. shop.catalog), settings (Settings › Site), or a label that makes a group of that name; false: not in the menu, reached by links. Default: Content. The admin |
layout | LayoutNode[] | Tabs, sections that fold and rows of the edit page, by field name. The admin |
badge | { where, tone?, label? } | A number beside its menu item: the documents matching where that the user may read, e.g. orders to send. |
count | boolean | The number of documents beside its menu item. Default true. |
empty | { description?, link? } | What the list says before it has documents, and a link ({ label, href }). |
list | { tree?, sort? } | The list page: tree names a relationship to the same collection to show a tree (top-level documents first, children open below); sort is the default order, e.g. 'title'. |
calendar | { start, end?, color? } | A Calendar mode beside the list: start and end are top-level date fields, color a select or relationship field (not hasMany) that colours documents. The admin |
ownerField | string | With auth.rbac: a relationship field to users naming the owner, e.g. 'author', for roles given "own documents only". Default: who created it (createdBy). Roles |
confirmDelete | { typeTitle?, impact? } | Deleting asks more: typeTitle has the user type the title; impact is a path under the API, called with ?id=, returning { message } about what goes too. No deleting from a selection. |
globals
| Option | Type | Default | |
|---|---|---|---|
slug | string | — | Required. |
fields | Field[] | — | Required. |
label | string | { en, th } | from the slug | |
drafts | boolean | false | |
versions | boolean | VersionsConfig | false | |
schedule | boolean | false | Needs drafts. |
httpCache | false | { sMaxAge?, staleWhileRevalidate? } | the config's | As for collections. |
preview | ({ doc, locale }) => string | null | — | |
access | { read?, update? } | logged in | |
scope | ({ context, user }) => string | null | undefined | — | One value per scope, e.g. per tenant: a string keeps a value of its own, undefined the shared one, null none (reads give it empty, changes are refused). |
hooks | GlobalHooks | — | See below. |
admin | ContainerAdmin | — | { icon, order, group, sidebar, layout }, as for collections. Default icon settings, group Settings › Site. |
Global hooks
| Hook | Arguments | Return |
|---|---|---|
beforeValidate | data, operation, previousDoc? | new data, or nothing |
beforeChange | data, operation, previousDoc? | new data, or nothing |
afterChange | doc, operation, previousDoc? | — |
afterRead | doc | new doc, or nothing |
endpoints
| Option | Type | |
|---|---|---|
path | string | Under routes.api, e.g. /seo/generate or /stats/:collection. The first segment can't be a collection slug or one the built-in API uses (reserved paths). |
method | 'get' | 'post' | 'put' | 'patch' | 'delete' | |
handler | (request: EndpointRequest) => unknown | Returns a Response, or a value sent as JSON. Endpoints |
root | boolean | Serve the path from the site's root (standalone server only), e.g. /robots.txt. Default false. |
EndpointRequest
| Property | Type | |
|---|---|---|
request | Request | The Web request. |
url | URL | Its URL. |
params | Record<string, string> | Values of :name segments. |
user | AuthUser | null | The logged-in user. |
context | RequestContext | The request's context (onRequest); pass it to the Local API with user. |
ip | string | undefined | The client's IP address, when the adapter knows it. |
cms | EasyCMS | The Local API. |
json | () => Promise<object> | The JSON body (an object, at most 1 MB). |
text | () => Promise<string> | The body as sent, as text (at most 1 MB), e.g. to check a webhook's signature. |