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
  • defaultLocale exists in locales
  • 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/:name and PUT /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 — GET returns null until the first PUT (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