Admin API
Admin conventions and client API reference — fields, views, pages, widgets, and configuration.
File Conventions
The admin module uses file conventions to discover configuration. Place these files in your admin directory.
sidebar(config)
import { sidebar } from "#questpie/factories";
export default sidebar({
sections: [
{ id: "content", title: { en: "Content", sk: "Obsah" } },
{ id: "settings", title: { en: "Settings" } },
],
items: [
// Collection item
{ sectionId: "content", type: "collection", collection: "posts" },
// Global item
{ sectionId: "settings", type: "global", global: "siteSettings" },
// External link
{
sectionId: "settings",
type: "link",
label: { en: "Documentation" },
href: "https://docs.example.com",
external: true,
icon: { type: "icon", props: { name: "ph:book-open" } },
},
],
});dashboard(config)
import { dashboard } from "#questpie/factories";
export default dashboard({
title: { en: "Dashboard" },
description: { en: "Overview of your content" },
columns: 3,
realtime: true,
sections: [
{ id: "stats", title: "Quick Stats" },
{ id: "recent", title: "Recent Activity" },
],
items: [
{ type: "stats", collection: "posts", label: "Total Posts", sectionId: "stats" },
{ type: "recentItems", collection: "posts", limit: 5, dateField: "createdAt", sectionId: "recent" },
],
});Dashboard Item Types
| Type | Properties |
|---|---|
stats | collection, filter, label |
value | loader, label, refreshInterval |
progress | loader, label, showPercentage |
chart | collection, field, chartType, label |
recentItems | collection, limit, dateField, label |
timeline | loader, maxItems, label |
branding(config)
import { branding } from "#questpie/factories";
export default branding({
name: { en: "My CMS", sk: "Moj CMS" },
logo: "/logo.svg",
});locale(config)
import { locale } from "#questpie/factories";
export default locale({
locales: [
{ code: "en", label: "English", fallback: true, flagCountryCode: "us" },
{ code: "sk", label: "Slovak", flagCountryCode: "sk" },
],
defaultLocale: "en",
});Client Factories
The admin client uses plain frozen factories to register field types, views, pages, and widgets.
field(name, config)
Register a field type for the admin UI. Maps a field type name to its form component and optional cell (list column) component.
import { field } from "@questpie/admin/client";
export default field("text", {
component: () => import("./text-field.js"),
cell: () => import("./cells/primitive-cells.js"),
});FieldDefinition shape:
| Property | Type | Description |
|---|---|---|
name | string | Field type name |
component | MaybeLazyComponent | Form field React component |
cell | MaybeLazyComponent | Optional list cell component |
view(name, config)
Register a view type. Views have a kind discriminant: "list" for collection list pages, "form" for edit/create pages.
import { view } from "@questpie/admin/client";
export default view("collection-table", {
kind: "list",
component: () => import("./table-view.js"),
});
export default view("collection-form", {
kind: "form",
component: () => import("./form-view.js"),
});ViewDefinition shape:
| Property | Type | Description |
|---|---|---|
name | string | View type name |
kind | "list" | "form" | View kind discriminant |
component | MaybeLazyComponent | React component |
page(name, config)
Register a custom admin page outside of collections/globals.
import { page } from "@questpie/admin/client";
export default page("analytics", {
component: () => import("./analytics-page.js"),
path: "/analytics",
showInNav: true,
});PageDefinition shape:
| Property | Type | Description |
|---|---|---|
name | string | Page identifier |
component | MaybeLazyComponent | React component |
path | string (optional) | Custom route path |
showInNav | boolean (optional) | Show in admin navigation |
widget(name, config)
Register a dashboard widget.
import { widget } from "@questpie/admin/client";
export default widget("stats", {
component: () => import("./stats-widget.js"),
});WidgetDefinition shape:
| Property | Type | Description |
|---|---|---|
name | string | Widget identifier |
component | MaybeLazyComponent | React component |
configureField(base, options)
Bridges a field registry entry (FieldDefinition) to a runtime instance (FieldInstance) by combining it with server-provided options.
import { configureField } from "@questpie/admin/client";
const instance = configureField(textFieldDef, {
label: "Title",
required: true,
placeholder: "Enter title...",
});This is used internally when mapping server introspection metadata to field rendering. You typically don't call this directly.
AdminState
The admin client state is a flat map containing all registered entities:
interface AdminState {
fields: Record<string, FieldDefinition>;
views: Record<string, ViewDefinition>;
pages: Record<string, PageDefinition>;
widgets: Record<string, WidgetDefinition>;
components: Record<string, MaybeLazyComponent>;
blocks: Record<string, MaybeLazyComponent>;
translations: Record<string, Record<string, string>>;
locale: string;
}The admin client config is auto-generated by codegen into .generated/client.ts. No manual setup is needed:
import admin from "@/questpie/admin/.generated/client";The generated config includes all registered fields, views, components, blocks, and built-in defaults.
Form View Configuration
Form Layout
v.collectionForm({
sidebar: {
position: "right",
fields: [f.status, f.publishedAt],
},
fields: [
f.title,
f.slug,
{
type: "section",
label: { en: "Content" },
description: { en: "Main content area" },
layout: "stack",
fields: [f.content, f.excerpt],
},
],
})Section Options
| Property | Type | Description |
|---|---|---|
type | "section" | Section discriminant |
label | string | i18n | Section heading |
description | string | i18n | Section description |
layout | "grid" | "stack" | Field layout within section |
columns | number | Grid columns (grid layout) |
fields | (Field | FieldConfig)[] | Fields in this section |
Field Config Options
Override field behavior per-form:
{
field: f.slug,
hidden: ({ data }) => !data.title,
readOnly: ({ data }) => data.status === "published",
compute: {
handler: ({ data }) => slugify(data.title),
deps: ({ data }) => [data.title],
debounce: 300,
},
}