Dyrected
Editor ExperienceCustom Components

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's component, or as a page's component.
  • You pass the actual component under that same key in the components prop 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 toUseRead
Replace the input for one fieldA custom field componentBelow
Add something above, below, or around built-in screensComponent slotsDashboard, List View
Show a set of records in a shape no built-in layout offersA custom view layoutCustom View Layouts
Replace the create or edit screen of a collectionA custom formCustom Create & Edit Forms
Add a screen that is not a record or a list of recordsA custom pageCustom Admin Pages

Once you have components, these pages cover the rest:

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:

PropWhat it is
valueThe current field value
onChangeCall it with the new value to update the form
fieldThe field's schema definition from your config
pathThe field's name
disabledtrue when the field is read-only or the user lacks update access
collectionThe 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 / afterDashboard slots and their props.
  • List View — the beforeList / beforeListTable / afterListTable / afterList slots 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 via admin.branding, browser tab titles via admin.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.

  • 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.

On this page

Dyrected| Cloud

Get your backend ready in minutes

Use a managed database, storage, APIs, and admin dashboard without setting up the infrastructure yourself.

Set Up My Backend