Config Reference
Complete reference for the cms.config.ts configuration file.
config()
The top-level configuration function:
import { config } from "@cms/config";
export default config({
locales?: string[]; // default: ["en"]
defaultLocale?: string; // default: first locale
cors?: {
origins: string[]; // allowed CORS origins
};
storage?: {
adapter: "local" | "s3" | "r2"; // default: "local"
};
entities: EntityDef[]; // your content entities
roles: RoleDef[]; // roles and permissions
admin?: AdminConfig; // admin UI configuration
});
Validation
The config builder validates:
- No duplicate entity names
defaultLocaleexists inlocales- Permission keys reference known entities
Built-in entities
These are auto-registered — you don’t define them:
_users— email, name, password (hidden), role_media— filename, mimeType, size, width, height, alt, url
entity()
import { entity } from "@cms/config";
entity(name: string, {
fields: AnyField[];
translatable?: boolean; // default: false
singleton?: boolean; // default: false
hooks?: EntityHooks;
}): EntityDef
Singleton entities
Setting singleton: true creates an entity with exactly one row. Use for site settings, navigation, footer content, etc.
const siteSettings = entity("site-settings", {
singleton: true,
fields: [
text("siteName"),
text("siteDescription"),
relation("logo", { to: media }),
],
})
- API:
GET /api/globals/:nameandPUT /api/globals/:name— no ID in the URL - Singletons with only optional fields are seeded with a null row on first boot. Singletons with required fields are not auto-seeded —
GETreturnsnulluntil the firstPUT(which performs an upsert) - Admin: appears in a “Globals” sidebar section, opens directly to the edit form (no list view)
- Permissions work the same as regular entities
Auto-injected fields
Every entity gets: id, createdAt, updatedAt.
Translatable entities additionally get: locale, translationGroup.
Reserved names
These field names are reserved and cannot be used: id, createdAt, updatedAt, locale, translationGroup.
Validation
Throws on duplicate field names within the same entity.
role()
import { role } from "@cms/config";
// Admin — full access, bypasses all checks
role(name: string, { admin: true }): RoleDef
// Public — unauthenticated access
role(name: string, {
public: true;
permissions: Record<string, CollectionPermissions>;
}): RoleDef
// Authenticated role
role(name: string, {
permissions: Record<string, CollectionPermissions>;
}): RoleDef
CollectionPermissions
{
create?: boolean | { filter?: Record<string, unknown>; fields?: { exclude?: string[] } };
read?: boolean | { filter?: Record<string, unknown>; fields?: { exclude?: string[] } };
update?: boolean | { filter?: Record<string, unknown>; fields?: { exclude?: string[] } };
delete?: boolean | { filter?: Record<string, unknown>; fields?: { exclude?: string[] } };
}
API keys
Static tokens for headless use — static site generators, CI/CD, server-to-server calls.
GET /api/_api-keys — list all keys (prefix, name, role, last used)
POST /api/_api-keys — create key
DELETE /api/_api-keys/:id — revoke key
All endpoints require an admin session.
Create a key
POST /api/_api-keys
Content-Type: application/json
{ "name": "Blog frontend", "role": "public", "expiresAt": "2027-01-01T00:00:00Z" }
Response (201):
{
"data": {
"id": "uuid",
"name": "Blog frontend",
"keyPrefix": "cms_aB3xK9m",
"role": "public",
"key": "cms_aB3xK9mNpQ2rS7vW1yZ..."
}
}
The raw key is shown once on creation and cannot be retrieved again. Revoke and recreate if lost. Keys are SHA-256 hashed in the database.
expiresAt is optional. When set, expired keys are automatically rejected. Omit it for keys that don’t expire.
Using a key
GET /api/posts?filter[status]=published
Authorization: Bearer cms_aB3xK9mNpQ2rS7vW1yZ...
The key assumes the role it was created with. All permission rules apply normally.
Keys can also be managed from the admin UI at Settings → API Keys.
AdminConfig
{
logo?: string;
title?: string;
theme?: Record<string, string>;
css?: string;
sidebar?: Array<{
label: string;
path: string;
icon?: string;
}>;
dashboard?: Array<() => Promise<unknown>>;
routes?: Array<{
path: string;
component: () => Promise<unknown>;
}>;
components?: {
field?: Record<string, () => Promise<unknown>>;
list?: Record<string, () => Promise<unknown>>;
};
}
Previous
Field Types Reference
Next
Migration CLI