Reference › Config
Docs/Reference/Config

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

OptionTypeDefault
secretstring—Required, 32+ characters. Signs sessions and preview tokens.
rateLimitobject—Requests per minute per IP, user and API key: { window, anonymous, users, apiKeys }, {} for the defaults (rate limits).
previousSecretsstring[]—Secrets used before secret: what they signed still verifies (rotating the secret).
dbDatabaseAdapter—Required. sqlite() or postgres(). Databases
serverURLstring—Public origin (https://example.com); makes media URLs absolute.
corsstring[] | '*'[]Origins whose browser code may call the API. Security
cronSecretstringCRON_SECRETBearer secret for GET <api>/jobs/run, at least 32 characters (a shorter CRON_SECRET is a warning). Scheduling
webhooksWebhookConfig[][]See webhooks.
eventsstring[][]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
jobsJobConfig[][]Work run with the scheduled jobs, e.g. a plugin's clean-up. See jobs.
timeZonestringthe server'sIANA 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
localizationLocalizationConfig | false—See localization.
routesRoutesConfigSee routes.
adminAdminConfigSee admin.
uploadUploadConfigSee upload.
authAuthConfigSee auth.
collectionsCollectionConfig[][]See collections.
globalsGlobalConfig[][]See globals.
endpointsEndpoint[][]See endpoints.
cliCommandsCliCommand[][]easy-cms <name> commands, e.g. from plugins: { name, description, help?, run({ cms, args, flags, log }) }. CLI
onRequestOnRequest | 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
apiKeysbooleanfalseAPI keys under Settings, for scripts and other apps. API keys
emailEmailAdapter—Sends email for plugins, e.g. smtp() or consoleEmail(). Email
backupsBackupsConfigby hand onlyDatabase backups on a schedule. See backups.
auditboolean | AuditConfigoffThe audit log: who changed what, sign-ins, admin actions. See audit.
countCacheSecondsnumber5Seconds a list's total (totalDocs, totalPages) is reused for the same query; the documents are always current. 0: count every time. Counting
maxPagenumberno limitThe deepest page the REST API serves to API clients (the signed-in admin keeps its pages); deeper requests get 400, pointing to cursor. Cursors
httpCacheHttpCacheConfigoff (no-store)Lets a CDN cache public reads of published content, with cache tags to purge by. See httpCache.
outboxbooleanfalseRuns 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
telemetryboolean | { traces?, metrics? }offOpenTelemetry spans and metrics through @opentelemetry/api, sent wherever the app's SDK sends them. Observability
searchSearchAdapter—Relevance-ranked search of the collections that have search, e.g. memorySearch(). Search
logsLogsConfigreadable lines{ format: 'json' } for one JSON object per line, with request ids. See logs.
pluginsPlugin[][](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
fieldTypesFieldTypeDefinition[][]Field types from packages, e.g. color from @easy-cms/fields. Custom field types

routes

OptionTypeDefault
apistring/api/cmsWhere the REST API is served.

admin

OptionTypeDefault
pathstring/adminWhere the admin is served.
locale'en' | 'th'enDefault admin language before a user picks one.
brandAdminBrand{}See brand.
siteURLstring/ (Nuxt, Next.js)The public site for "View site": a path or an http(s) URL.
navNavGroup[]—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.
modulesstring[][]Admin modules with Web Components: package exports or paths. Admin components
pagesAdminPage[][]Pages of their own at <admin>/p/<path>, e.g. from plugins. See pages.
dashboardDashboardWidget[][]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

OptionType
namestringShown in the menu, login page and browser tab. Default "Easy CMS".
logostringA path on your site or an https:// URL.
colorstringMain color as #rrggbb; shades are derived.

pages

OptionType
pathstringRequired. Lowercase letters, digits and -, unique: the page is at <admin>/p/<path>.
componentAdminComponentRequired. The page's body; the admin draws the header.
labelstring | { en, th }Required. Title in the header, the menu and the browser tab.
iconAdminIconMenu icon. Default file-text.
group'content' | 'settings' | false | stringIts menu group: content, settings, a group's id (admin.nav) or a label; false: not listed. Default content.
ordernumberIts place in its menu group: lower first. Those without one come after, in config order.
access({ user }) => booleanWho may open it, checked on the server. Default: every logged-in user.

dashboard

OptionType
idstringWhat roles are given in Settings → Roles (widget:<id>), so a panel keeps them when its tag changes. Default: the component's tag.
componentAdminComponentRequired. The panel's content.
width'half' | 'full'half (default): the side column; full: below both columns. One column on phones.
labelLabelIts name in Settings → Roles (auth.rbac). Default: the component's tag.
access({ user }) => booleanWho sees it, checked on the server. Default: every logged-in user.

Pages and dashboard panels shows how to write them.

backups

OptionTypeDefault
frequency'daily' | 'weekly'—Back up automatically. Without it, admins back up by hand.
atstring03:00When, as HH:MM in the server's time zone.
keepnumber7Finished backups to keep; older ones are deleted.
dirstringbackupsFolder for the default local storage. Never served publicly.
storageStorageAdapterlocal diskE.g. s3Storage() with a private bucket. Public URLs log a warning.
encryptionKeystring—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

OptionTypeDefault
keepDaysnumber365Days to keep entries; older ones are deleted. 0: keep them all.
valuesbooleantrueKeep values before and after a change; false: only which fields changed.
failedLoginsnumber20Failed 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

OptionTypeDefault
sMaxAgenumber60Seconds a CDN serves a public read without asking again (s-maxage).
staleWhileRevalidatenumber300Seconds 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

OptionTypeDefault
format'pretty' | 'json''pretty'json: one object per line (time, level, message, requestId, error). EASY_CMS_LOG_FORMAT wins over it. Logs
requestsbooleantrueWith json, a line for every API request: method, path, status, duration, user.

auth

OptionTypeDefault
rolesstring[]['admin', 'editor']Must include admin. With rbac, the roles that always exist. Users & auth
rbacbooleanfalseRoles and their permissions from the admin (Settings → Roles), on top of access rules. Roles
providersAuthProvider[][]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).
passwordboolean | { breakGlass }truefalse: only admins sign in with a password; { breakGlass: [emails] }: only those accounts (single sign-on for everyone). Everyone else uses providers.
setupCodestringEASY_CMS_SETUP_CODEThe 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
tokenExpirationnumber604800 (7 days)Session lifetime in seconds.
maxLoginAttemptsnumber5Failed logins allowed per email (and IP) within lockWindow.
lockWindownumber900 (15 minutes)In seconds.
trustedOriginsstring[][]Other origins that may send cookie-authenticated requests.
resetPasswordExpirationnumber3600Seconds a "forgot password" link works. Links need email, and serverURL in production. Forgotten passwords
inviteExpirationnumber604800Seconds an invitation link works.
emails{ resetPassword?, invite?, passwordChanged? }—Functions ({ user, url, locale, expiresAt }) => { subject, text, html? } for your own email text.
membersMembersConfig—People who sign in on the site, not the admin, e.g. customers. See members.

members

OptionType
rolesstring[]Their roles, also in roles. Members never get into the admin, and isStaff (every collection's default) doesn't count them. Site members
signUpMembersSignupVisitors 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

OptionTypeDefault
rolestring—The role new accounts get: one of members.roles.
verifyEmailbooleantrueNew accounts confirm their email with a link before signing in.
turnstile{ siteKey, secretKey }—Cloudflare Turnstile, checked on every sign-up.

upload

OptionTypeDefault
dirstringuploadsFolder for the default local storage, from the project root.
maxFileSizenumber10485760 (10 MB)In bytes.
mimeTypesstring[]['image/*', 'application/pdf']Allowed types, detected from file contents: MIME types, type/*, or documents, office, archives. See File types.
storageStorageAdapterlocal diskE.g. s3Storage() from @easy-cms/storage-s3. Uploads
imageSizesImageSize[][]Resized copies (needs sharp). See image sizes.
fromURLUploadFromURLConfigoffUploads from links. See fromURL.
privateStorageStorageAdapterstorage without public URLsWhere files in private folders go. See Private folders.
foldersbooleanfalseFolders in the media library, and with auth.rbac who can use each. See Folders.

fromURL

OptionType
allowedHostsstring[]Required. images.example.com, *.example.com (subdomains) or * (any public host).
allowPrivatebooleanAlso private network addresses (localhost, 10.x…). Default false. Read first

Image sizes

OptionTypeDefault
namestring—Key in media.sizes, e.g. thumbnail.
widthnumber—Width in pixels.
heightnumber—Optional height.
fit'cover' | 'contain' | 'inside'coverHow to fit when both width and height are set.

localization

OptionTypeDefault
localesstring[]—Content locales, e.g. ['th', 'en']. Localization
defaultLocalestringthe first localeUsed when no locale is given; its values fill empty ones.
fallbackbooleantrueReads return the default locale's value when a value is empty.

webhooks

OptionTypeDefault
urlstring—Where to POST events. Webhooks
eventsstring[]allcreate, update, delete, publish, unpublish, draft.
collectionsstring[]all but usersCollections to send events for; [] for none. List users to get user accounts.
globalsstring[]allGlobals to send events for; [] for none.
secretstring—Signs each attempt: x-easy-cms-timestamp and x-easy-cms-signature-v2 (checking it).
headersRecord<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

OptionType
namestringUnique, lowercase with - and :, e.g. shop:carts.
everynumberAt most this often, in seconds; when each last ran is kept in the database. With at, whole days (default one). Default: every run.
atstringA time of day, HH:MM in timeZone. A missed time runs once when jobs next run.
adminSchedulebooleanAdmins may change every and at on Settings → Jobs. Default false.
run({ cms, now }) => unknownThe 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

OptionTypeDefault
slugstring—Required. URL and table name: lowercase letters, digits, -, _.
fieldsField[]—Required. See the field reference.
labels{ singular?, plural? }from the slugEach a string or { en, th }.
useAsTitlestring—Top-level field shown as the document title.
draftsbooleanfalseAdds status (draft | published). Drafts
versionsboolean | VersionsConfigfalseKeep a version of every save. See versions.
schedulebooleanfalsePublish and unpublish at a set time (needs drafts).
httpCachefalse | { sMaxAge?, staleWhileRevalidate? }the config'sThis 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 inAccess control
hooksCollectionHooks—See hooks.
adminCollectionAdmin—See admin components.

versions

OptionTypeDefault
keepnumber50Versions kept per document; older ones are deleted.

hooks

Each is a list of functions. Hooks

HookArgumentsReturn
beforeValidatedata, operation, previousDoc?new data, or nothing
beforeChangedata, operation, previousDoc?new data, or nothing
afterChangedoc, operation, previousDoc?—
beforeDeleteid—
afterDeleteid, doc—
afterReaddocnew doc, or nothing

Every hook also gets user, cms and slug.

Admin components

OptionType
iconAdminIconMenu icon. Default file-text. Branding
ordernumberIts 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.
sidebarSidebarPanel[]Panels in the edit page's side column; { tag, props, position: 'top' } puts one above the rest. Admin components
groupstring | Label | falseIts 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
layoutLayoutNode[]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.
countbooleanThe 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
ownerFieldstringWith 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

OptionTypeDefault
slugstring—Required.
fieldsField[]—Required.
labelstring | { en, th }from the slug
draftsbooleanfalse
versionsboolean | VersionsConfigfalse
schedulebooleanfalseNeeds drafts.
httpCachefalse | { sMaxAge?, staleWhileRevalidate? }the config'sAs 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).
hooksGlobalHooks—See below.
adminContainerAdmin—{ icon, order, group, sidebar, layout }, as for collections. Default icon settings, group Settings › Site.

Global hooks

HookArgumentsReturn
beforeValidatedata, operation, previousDoc?new data, or nothing
beforeChangedata, operation, previousDoc?new data, or nothing
afterChangedoc, operation, previousDoc?—
afterReaddocnew doc, or nothing

endpoints

OptionType
pathstringUnder 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) => unknownReturns a Response, or a value sent as JSON. Endpoints
rootbooleanServe the path from the site's root (standalone server only), e.g. /robots.txt. Default false.

EndpointRequest

PropertyType
requestRequestThe Web request.
urlURLIts URL.
paramsRecord<string, string>Values of :name segments.
userAuthUser | nullThe logged-in user.
contextRequestContextThe request's context (onRequest); pass it to the Local API with user.
ipstring | undefinedThe client's IP address, when the adapter knows it.
cmsEasyCMSThe 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.