Custom Views
Create custom list and form view types for the admin panel.
Create custom view types beyond the built-in table and form views -- kanban boards, calendars, galleries, or domain-specific layouts.
Overview
A custom view requires two parts:
- View definition -- register the view using the
view()factory with akinddiscriminant - React component -- the actual rendering logic
Views have two kinds:
"list"-- displayed on the collection list page (e.g., table, kanban, gallery)"form"-- displayed on the collection edit/create page (e.g., form, builder)
Defining a Custom View
File Convention
Create a view definition file using the view() factory:
import { view } from "@questpie/admin/client";
export default view("kanban", {
kind: "list",
component: () => import("./kanban-view.js"),
});After codegen, this view is registered in the admin client and available for use.
React Component
import type { CollectionListViewProps } from "@questpie/admin/client";
type KanbanItem = {
id: string;
_title?: string;
} & Record<string, unknown>;
export function KanbanView({ config, onRowClick }: CollectionListViewProps) {
const data = (config?.data ?? []) as KanbanItem[];
const statusColumn = config?.groupBy || "status";
const groups = groupByField(data, statusColumn);
return (
<div style={{ display: "flex", gap: 16, overflowX: "auto" }}>
{Object.entries(groups).map(([status, items]) => (
<div key={status} style={{ minWidth: 280, flex: "0 0 280px" }}>
<h3 style={{ fontWeight: 600, marginBottom: 8 }}>
{status} ({items.length})
</h3>
<div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
{items.map((item) => (
<div
key={item.id}
onClick={() => onRowClick?.(item)}
style={{
padding: 12,
borderRadius: 8,
border: "1px solid #e5e7eb",
cursor: "pointer",
}}
>
<div style={{ fontWeight: 500 }}>{item._title}</div>
</div>
))}
</div>
</div>
))}
</div>
);
}
function groupByField(data: KanbanItem[], field: string) {
const groups: Record<string, KanbanItem[]> = {};
for (const item of data) {
const value = item[field];
const key =
typeof value === "string" || typeof value === "number"
? String(value)
: "unknown";
(groups[key] ??= []).push(item);
}
return groups;
}Using Custom Views
Once registered, reference the view in your collection's .list() configuration:
collection("tasks")
.fields(({ f }) => ({
title: f.text().required(),
status: f.select([
{ value: "todo", label: "Todo" },
{ value: "in-progress", label: "In Progress" },
{ value: "done", label: "Done" },
]),
assignee: f.relation("users"),
}))
.list(({ v }) =>
v.kanban({
groupBy: "status",
}),
);The view configuration object is passed to your component as config in the view context.
Custom Form Views
Form views work the same way but use kind: "form":
import { view } from "@questpie/admin/client";
export default view("page-builder", {
kind: "form",
component: () => import("./page-builder-view.js"),
});import type { CollectionFormViewProps } from "@questpie/admin/client";
export function PageBuilderView(props: CollectionFormViewProps) {
return <div>{/* Custom drag-and-drop page builder UI */}</div>;
}Use in a collection:
.form(({ v }) => v.pageBuilder({
// Custom config
}))Module Registration
To distribute custom views as part of a module:
import { module } from "questpie/app";
export const kanbanModule = module({
name: "kanban-views",
// Views are discovered by file convention through the admin plugin
// The module can include the view files in its package
});The admin plugin's codegen automatically discovers view files in the configured directories.
Related Pages
- List Views -- Built-in list views
- Form Views -- Built-in form views
- Admin API -- Admin client factories