QUESTPIE
Reference

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

TypeProperties
statscollection, filter, label
valueloader, label, refreshInterval
progressloader, label, showPercentage
chartcollection, field, chartType, label
recentItemscollection, limit, dateField, label
timelineloader, 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:

PropertyTypeDescription
namestringField type name
componentMaybeLazyComponentForm field React component
cellMaybeLazyComponentOptional 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:

PropertyTypeDescription
namestringView type name
kind"list" | "form"View kind discriminant
componentMaybeLazyComponentReact 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:

PropertyTypeDescription
namestringPage identifier
componentMaybeLazyComponentReact component
pathstring (optional)Custom route path
showInNavboolean (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:

PropertyTypeDescription
namestringWidget identifier
componentMaybeLazyComponentReact 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

PropertyTypeDescription
type"section"Section discriminant
labelstring | i18nSection heading
descriptionstring | i18nSection description
layout"grid" | "stack"Field layout within section
columnsnumberGrid 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,
  },
}

On this page