Configuration

The cms.config.ts file is the single entry point for all CMS configuration.

Everything is configured through a single cms.config.ts file. The engine loads this file at startup and uses it to build the database schema, register API routes, set up permissions, and configure the admin UI.

Config structure


          import { config, entity, role, text, slug, select, relation, object, blocks } from "@cms/config";

export default config({
  // Locales for i18n (optional)
  i18n: {
    locales: [
      { label: "English", value: "en" },
      { label: "Portuguese", value: "pt" },
      { label: "French", value: "fr" },
    ],
    defaultLocale: "en",
  },

  // CORS origins for external frontends (optional)
  cors: {
    origins: ["https://example.com"],
  },

  // Media storage adapter (optional, defaults to local)
  storage: { adapter: "local" },
  // For S3: { adapter: "s3", bucket, region, accessKeyId, secretAccessKey, endpoint? }

  // Your content entities — add global: true for singleton entries
  entities: [posts, pages, authors, siteSettings],

  // Roles and permissions
  roles: [admin, editor, publicRole],
});
        

Built-in entities

CMS automatically registers four built-in entities that you don't need to define:

_user


          // Auto-registered with these fields:
// - email (text, unique, required)
// - name (text)
// - role (text, required)
// Plus automatic: id, _type, createdAt, updatedAt
        

Authentication uses magic-link email codes — no password column. If you need a credential column for a custom auth scheme, add a field on a derived role and write to it from a beforeCreate hook (clients never get to set it).

_image, _video, _file

The three asset entities. All uploads go through POST /api/assets/upload and are routed to the correct entity by MIME type.


          // _image — fields: filename, mimeType, size, width, height, alt, url
// _video — fields: filename, mimeType, size, width, height, url
// _file  — fields: filename, mimeType, size, url
// All include automatic: id, createdAt, updatedAt
        

Reference them in your entity config with the typed field helpers:


          import { image, video, file } from "@cms/config";

image("featuredImage")
video("demoVideo")
file("attachment", { multiple: true })
        

See the Assets guide for the full upload API, image transforms, and querying reference.

Automatic fields

Every entity automatically gets:

  • id — UUID primary key
  • _type — set to the entity name (e.g. "posts", "authors")
  • createdAt — timestamp, set on insert
  • updatedAt — timestamp, set on insert and update

You never define these — they're always present.

Admin UI configuration

The admin UI is configured through the admin key:


          import { BookOpenIcon, GearIcon } from "@phosphor-icons/react";
import { MyDashboard } from "./my-dashboard";

export default config({
  admin: {
    logo: "/logo.svg",
    title: "My CMS",
    sidebar: ({ group, item, divider, builtins }) => [
      builtins.DASHBOARD,
      group("Content", {
        collapsible: true,
        children: [
          item("posts", { label: "Posts", icon: BookOpenIcon }),
          item("authors", { label: "Authors" }),
        ],
      }),
      divider(),
      item("settings", { label: "Settings", icon: GearIcon }),
      divider(),
      builtins.ASSETS,
    ],
    dashboard: MyDashboard,
  },
  // ...
});
        

See Admin Extensibility for the full guide.

Entity-level admin config

Each entity can carry its own admin options via the admin key on entity(...):


          import { entity, slug } from "@cms/config";
import { adminLivePreview } from "@cms/live-preview";

const page = entity("page", {
  admin: {
    preview: ({ data }) =>
      adminLivePreview({
        data,
        url: `http://localhost:3001/${data.slug}`,
      }),
  },
  fields: [slug("slug"), /* ... */],
});
        
  • preview — a function rendered alongside the form when the editor clicks Preview. The data parameter is fully typed to your entity's fields. Use adminLivePreview for an iframe with real-time postMessage sync, or return any custom React.ReactNode.

See Live Preview for the full setup guide.

Field-level conditional UI

Individual fields can react to the rest of the form or the current user via admin.visible and admin.readOnly. Common uses: show a "publishedAt" date only when status === "published", hide an internal-notes field from non-editors, or make approvedBy read-only outside the admin role.


          date("publishedAt", {
  admin: { visible: ({ data }) => data.status === "published" },
}),
        

See Field visibility and access for the full pattern, including the difference between hiding in the UI and blocking at the API.

Previous

Installation

Next

Fields