Admin Extensibility

Four tiers of customization for the React admin UI.

The admin UI is designed around composition, not overrides. Customizations inject into a working system — you never rebuild a component from scratch to change one thing.

Tier 1 — Config values

Simple branding and theming. No React knowledge needed.


          export default config({
  admin: {
    logo: "/logo.svg",
    title: "My CMS",
    theme: {
      primary: "oklch(0.7 0.15 250)",
    },
    css: "./admin.css",
  },
  // ...
});
        

The admin uses CSS custom properties for all design tokens. theme overrides these variables. css is an escape hatch for anything else — loaded after the default styles.

Tier 2 — Injection slots

Add to the UI without replacing anything. The sidebar, navigation, auth, and routing all keep working.


          admin: {
  sidebar: [
    { label: "Analytics", path: "/analytics", icon: "ChartBar" },
  ],
  dashboard: [() => import("./RevenueWidget")],
  routes: [
    { path: "/analytics", component: () => import("./AnalyticsDashboard") },
  ],
}
        
  • sidebar — adds navigation items. The built-in collection list stays, your items are appended.
  • dashboard — adds widgets to the home screen.
  • routes — registers new pages at custom paths, with full access to auth context and CMS hooks.

Tier 3 — Component overrides

Replace a specific, scoped piece — not the whole page. The form, validation, saving, and permissions are still handled by the framework.


          admin: {
  components: {
    field: {
      color: () => import("./ColorPicker"),
      "posts.hero": () => import("./HeroEditor"),
    },
    list: {
      products: () => import("./ProductGrid"),
    },
  },
}
        

Field overrides

Custom field components receive the same props as built-in ones:


          type FieldProps = {
  value: unknown;
  onChange: (value: unknown) => void;
  field: FieldDef;
  error?: string;
  disabled?: boolean;
};
        

Override by field type (color matches all color fields) or by entity.field key (posts.hero matches only the hero field on posts).

List overrides

Custom list components replace the default table view for a specific collection.

Tier 4 — Hooks as public API

The same hooks the built-in UI components use are exported as public API from @cms/ui:


          import { useDocuments, useDocument, useAuth, useCMS } from "@cms/ui";

function RevenueWidget() {
  const { data } = useDocuments("orders", {
    filter: { status: "paid" },
    limit: 100,
  });
  return <div>Revenue: {sum(data)}</div>;
}
        

Available hooks

  • useCMS() — config, entities, locales
  • useAuth() — current user, role, logout
  • useDocuments(collection, params) — list query with filtering/sorting/pagination
  • useDocument(collection, id) — single document fetch
  • useEntity(name) — entity definition and field metadata

Custom components built with these hooks are first-class citizens — they use the same data fetching, caching, and auth as the built-in UI.

Design principle

Each tier is additive:

  1. Tier 1: change values
  2. Tier 2: inject into existing structure
  3. Tier 3: swap a specific leaf component
  4. Tier 4: build anything with the same primitives

You never have to “rebuild the sidebar.” You either add to it (Tier 2) or swap a single component while keeping all the data/auth/routing plumbing (Tier 3 + Tier 4 hooks).

Previous

Media

Next

Deployment