Overview
Swap your own React or Vue components into the admin: field inputs, dashboard and list slots, whole view layouts, and full pages. Learn the pattern they all share.
The admin is generated from your schema, but you're not stuck with the built-in look of every piece. Dyrected gives you four extension points: you can replace a field's input, inject components into slots on the dashboard and views, draw a view's records with your own layout, and add whole pages to the sidebar. This page is the map. It explains the registration pattern they all share, points you to the page for each one, and is honest about what you can't customize this way.
Throughout, "your app" means the project you embedded the admin into — your Next.js, Nuxt, Vue, or React application.
The mental model
Every custom component follows the same two-step pattern: declare where it goes, then provide the component.
- You reference a component by a string key in your config: on a field (
admin.component), in a slot list or form setting (admin.components), as a view'scomponent, or as a page'scomponent. - You pass the actual component under that same key in the
componentsprop of<DyrectedAdmin>.
There's no import map, no build step, and no string module paths. You hand real component references to the admin, and it renders them where your config said to. If a key has no matching component, the admin shows a placeholder and warns in development (see Registration warnings); if a custom component throws, an error boundary isolates it so the rest of the admin keeps working.
There are five extension points:
| You want to | Use | Read |
|---|---|---|
| Replace the input for one field | A custom field component | Below |
| Add something above, below, or around built-in screens | Component slots | Dashboard, List View |
| Show a set of records in a shape no built-in layout offers | A custom view layout | Custom View Layouts |
| Replace the create or edit screen of a collection | A custom form | Custom Create & Edit Forms |
| Add a screen that is not a record or a list of records | A custom page | Custom Admin Pages |
Once you have components, these pages cover the rest:
- Admin Hooks: read and write data as the signed-in editor, navigate, show toasts, and check access.
- Styling Custom Components: match the admin in light and dark mode.
- Typed Component Keys: catch a misspelled or missing key at compile time.
- Registration warnings: what the admin shows when a component is missing, unused, or throws.
- Custom Create & Edit Forms: replace a collection's create or edit screen and keep Dyrected's save pipeline.
- Replacing a Built-in Layout: swap the kanban, table, or another layout everywhere.
- Nuxt Auto-Registration: register components by dropping files in a folder.
Custom field components
When a field needs an input the built-in set doesn't cover — a color picker, a map, a code editor — point the field at your own component. Set admin.component to a key of your choosing on the field:
export const Products = defineCollection({
slug: "products",
fields: [
{
name: "brandColor",
label: "Brand color",
type: "text",
admin: {
component: "brand-color-picker",
description: "Brand hex color, e.g. #6366f1",
},
},
],
});Then register the component under the same key in the components.fields map when you mount the admin:
import { DyrectedAdmin } from "@dyrected/next/admin";
import { BrandColorPicker } from "./BrandColorPicker";
export default function AdminPage() {
return (
<DyrectedAdmin
components={{
fields: { "brand-color-picker": BrandColorPicker },
}}
/>
);
}The key is yours to name — it's matched by the admin.component string, not by field type. That means a custom component applies to the specific field (or fields) that opt into it, not to every field of a given type.
What your component receives
Your field component is handed everything it needs to read and write the value, plus context about where it's rendering:
| Prop | What it is |
|---|---|
value | The current field value |
onChange | Call it with the new value to update the form |
field | The field's schema definition from your config |
path | The field's name |
disabled | true when the field is read-only or the user lacks update access |
collection | The slug of the collection the field belongs to |
context | { user, schemas, siblingData } — the current user, all loaded schemas, and the sibling field values in the same document |
Read from value, call onChange to update, and use context.siblingData if your input depends on other fields. Because the admin resolves access before rendering, honoring disabled is usually all you need for permissions.
Component slots
Slots are fixed positions where you can add components without rebuilding a whole screen — a banner above the dashboard, a header above a collection list, a widget after a table. They use the same declare-then-provide pattern, but you declare them in an admin.components object rather than on a field.
Declare the slot keys in config — on defineConfig for the dashboard, or on a collection for its list:
export const Posts = defineCollection({
slug: "posts",
admin: {
components: {
beforeList: ["posts-header"],
},
},
fields: [...],
});
export default defineConfig({
collections: [Posts],
admin: {
components: {
beforeDashboard: ["welcome-banner"],
},
},
});Then register the components, grouped by where they live:
<DyrectedAdmin
components={{
dashboard: { "welcome-banner": WelcomeBanner },
collectionList: { "posts-header": PostsHeader },
}}
/>Each slot list is an array, so you can stack more than one component in a slot. The two slot groups get different props suited to where they render — the dashboard slots receive the client, user, and schemas; the list slots also receive the collection, its documents, pagination, and permissions. Each group has its own page:
- Dashboard — the
beforeDashboard/afterDashboardslots and their props. - List View — the
beforeList/beforeListTable/afterListTable/afterListslots and their props.
Operational views have their own slots, beforeViewHeader, afterViewHeader, beforeViewContent, and afterViewContent. Declare them on a single view with components in defineView, or on a collection with admin.components to apply to all of its views, and register the components in the collectionView group:
defineView({
slug: "pipeline",
label: "Pipeline",
layout: "kanban",
groupBy: "status",
components: { beforeViewContent: ["pipeline-legend"] },
});<DyrectedAdmin
components={{ collectionView: { "pipeline-legend": PipelineLegend } }}
/>The components you register under collectionList also work in these slots, so existing list slots keep working inside views. See List View for how collection-wide and view-specific slots combine, and the props they receive.
Custom view layouts
When your records need to be displayed in a shape that the built-in layouts (table, cards, kanban, spreadsheet, calendar) do not support — like a seating chart, an interactive map, or a timeline — you can create a custom view layout.
Declare the layout key in your collection's views in dyrected.config.ts, then provide the component in components.viewLayouts:
<DyrectedAdmin
components={{
viewLayouts: {
"seating-chart": SeatingChartLayout,
},
}}
/>Custom layouts receive the current view context and can read or update data using the useCollectionView hook. See Custom View Layouts.
Custom admin pages
When you need an operational screen that isn't tied to a single collection — an executive dashboard, an onboarding wizard, a billing hub, or an analytics panel — add a custom page to the admin navigation with definePage().
<DyrectedAdmin
components={{
pages: {
"ops.executiveHub": OperationsDashboard,
},
}}
/>Custom pages support dynamic header actions, breadcrumbs, sub-routing, and notifications. See Custom Admin Pages.
Using Vue components
If your app is Vue or Nuxt, you register Vue components exactly the same way — pass them in the components prop (or via defineAdminComponents). The Vue integration wraps them automatically so they render inside the admin interface; you don't do anything special beyond passing them. The props described above arrive as Vue props.
What you can't customize this way
It's worth being clear about the edges so you don't go looking for something that isn't there:
- The root admin shell: You cannot replace the core shell (the sidebar frame, header bar, or authentication screen) with arbitrary JSX/Vue components. You customize these declaratively: navigation structure via
admin.navigation, logos viaadmin.branding, browser tab titles viaadmin.meta, and visual styles via custom theme variables. - Internal provider injection: You can wrap
<DyrectedAdmin>in any providers your app needs (QueryClientProvider, etc.), but there is no configuration option to inject arbitrary React context providers inside the admin's internal routing tree. - The save pipeline: You can replace the create and edit screens with your own form, but a custom form still saves through Dyrected's write path, so validation, access rules, and hooks always run. The read-only Detail View is separate and can embed your own components. If you need a workflow that is not a record at all, reach for a custom admin page.
If your goal is theming rather than swapping components, that's Customizing CSS.
Recommended path
- Reach for a custom field component when a field needs an input the defaults don't offer.
- Reach for a slot to add widgets or banners alongside built-in dashboard or collection screens.
- Reach for a custom form when a create or edit screen needs a different shape, like a walk-in desk or a scanner, but should still save through Dyrected.
- Reach for a view layout when collection records need a custom visual presentation (e.g. maps, timelines, seating charts).
- Reach for a custom admin page when you need a dedicated operational screen, wizard, or analytics dashboard with its own URL and navigation item.
Customizing CSS
Match the admin to your brand with a few config options, then reach for CSS variables when you need full control of the theme, in light and dark mode.
Custom Admin Pages
Add a screen of your own to the admin sidebar, such as a door check-in station or a reconciliation tool, with the admin's header, breadcrumbs, and navigation around it, in React or Vue.