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, localesuseAuth()— current user, role, logoutuseDocuments(collection, params)— list query with filtering/sorting/paginationuseDocument(collection, id)— single document fetchuseEntity(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:
- Tier 1: change values
- Tier 2: inject into existing structure
- Tier 3: swap a specific leaf component
- 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