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 { useListViewContext } from "@questpie/admin/client";
export function KanbanView() {
const { data, columns, config, onRecordClick } = useListViewContext();
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={() => onRecordClick(item.id)}
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: any[], field: string) {
const groups: Record<string, any[]> = {};
for (const item of data) {
const key = item[field] || "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: true }),
status: f.select({ options: ["todo", "in-progress", "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 { useFormViewContext } from "@questpie/admin/client";
export function PageBuilderView() {
const { data, onChange, fields } = useFormViewContext();
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";
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