Dyrected
Editor ExperienceCustom Components

List View

Inject your own components around a collection's list — a header, a banner above the table, a summary below it — using the four list slots.

The collection list is where editors browse, filter, and act on records. You can't rebuild it, but you can add your own components around it: a header with a custom action, a banner above the table, a summary row or custom pagination below. Dyrected gives you four slots on every collection list for this — now aliased as operational view slots so the same content mounts inside table/kanban/calendar/cards/spreadsheet workspaces. See Routing and preferences for the mapping.

By the end of this page you should know the four list slots (and their view aliases), the rich set of props your slot components receive, and how to register them per collection. This page assumes the declare-then-provide pattern from the Custom Components overview.

The four slots (and their view aliases)

A collection list has four injection points, top to bottom. The names below are the legacy beforeList keys — they still work inside operational views via an alias, with new beforeView* names preferred:

LegacyView alias (preferred)Where it renders
beforeListbeforeViewHeaderAbove everything, including the toolbar
beforeListTablebeforeViewContentBetween the toolbar and the table/grid
afterListTableafterViewContentDirectly below the table/grid
afterListafterViewContentAt the very bottom

Dyrected seamlessly bridges beforeList to beforeViewHeader and other slot keys, ensuring existing custom component registrations continue functioning across all operational layouts.

Each slot accepts an array of component keys so you can stack multiple extensions. Declare slot components per collection under admin.components:

import { defineCollection, defineTextField } from "@dyrected/core";

export const Posts = defineCollection({
  slug: "posts",
  admin: {
    components: {
      beforeList: ["posts-header"],
      afterListTable: ["posts-summary"],
    },
  },
  fields: [
    defineTextField({ name: "title", label: "Title", required: true }),
    defineTextField({ name: "slug", label: "Slug", unique: true }),
  ],
});

Then register the components under the collectionList group when you mount the admin, matched by the same keys:

import { DyrectedAdmin } from "@dyrected/next/admin";
import { PostsHeader } from "./PostsHeader";
import { PostsSummary } from "./PostsSummary";

export default function AdminPage() {
  return (
    <DyrectedAdmin
      components={{
        collectionList: {
          "posts-header": PostsHeader,
          "posts-summary": PostsSummary,
        },
      }}
    />
  );
}

The registry key is the slot-key name you declared ("posts-header"), not the collection slug — the component renders wherever that key appears in a collection's slot lists. A key with no matching component is skipped (with a dev warning), and a component that throws is isolated by an error boundary so the list still renders.

These slots also appear on upload (media) collections — a upload: true collection's list uses the same four slots, declared the same way on its admin.components.

What your component receives

List slot components get everything about the current list — the data, its loading and pagination state, the user's permissions, and links — so they can render summaries, actions, or context without re-fetching:

PropWhat it is
clientThe authenticated Dyrected client, for any extra API calls
userThe signed-in user document, or null
collectionThe full config of the collection being listed
collectionSlugThe collection's slug (e.g. "posts")
responseThe raw paginated response from the last query (or undefined while loading)
documentsThe documents currently shown, unwrapped from response
isLoadingtrue while the list query is in flight
pagination{ page, totalPages, total, hasNextPage, hasPrevPage }
permissions{ canRead, canCreate } for the current user on this collection
urls{ collection, create } — admin links to the list and the new-document form

For example, a summary that shows the total count below the table:

import type { CollectionListSlotProps } from "@dyrected/admin";

export function PostsSummary({ pagination, isLoading }: CollectionListSlotProps) {
  if (isLoading) return null;
  return (
    <div className="dy-px-4 dy-py-2 dy-text-sm dy-text-muted-foreground">
      {pagination.total} posts total
    </div>
  );
}

Because you receive documents, pagination, and permissions, most list add-ons need no data fetching of their own — read what's already there. Reach for client only when you need something the list didn't load.

Slot props intentionally don't expose setters for the list's filters, sorting, or row selection — they're for adding content alongside the list, not driving its internal state. Use client if you need to run your own query.

Using Vue components

In a Vue or Nuxt app, register Vue components in the same collectionList map; they're wrapped automatically and receive the props above as Vue props. See the overview.

Use beforeList for a header or collection-level action, beforeListTable for a banner or notice, and afterListTable / afterList for summaries and totals. Read from the props you're given — documents, pagination, permissions — before reaching for client. For components on the dashboard instead, see Dashboard.

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