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:
| Legacy | View alias (preferred) | Where it renders |
|---|---|---|
beforeList | beforeViewHeader | Above everything, including the toolbar |
beforeListTable | beforeViewContent | Between the toolbar and the table/grid |
afterListTable | afterViewContent | Directly below the table/grid |
afterList | afterViewContent | At 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:
| Prop | What it is |
|---|---|
client | The authenticated Dyrected client, for any extra API calls |
user | The signed-in user document, or null |
collection | The full config of the collection being listed |
collectionSlug | The collection's slug (e.g. "posts") |
response | The raw paginated response from the last query (or undefined while loading) |
documents | The documents currently shown, unwrapped from response |
isLoading | true 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.
Recommended path
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.
Dashboard
Add your own components above and below the built-in dashboard — a welcome banner, an analytics widget, a shortcut panel — using the dashboard slots.
Preferences
How the admin remembers each editor's view, how a team-wide default cascades, and how to read or write preferences from your own code.