QUESTPIE
AdminCustom views

Custom pages

A page is one React file under the admin that gets its own URL. It answers to no collection, so nothing about it is generated and nothing on the server declares it.

View markdown

An import log. A reconciliation report. A screen that reads three collections and writes none. Those are pages.

One file

Put it in src/questpie/admin/pages/. The directory does not exist in a fresh app, so create it.

src/questpie/admin/pages/reports.tsx
import { AdminViewHeader, AdminViewLayout, page } from "@questpie/admin/client";

function ReportsPage() {
	return (
		<AdminViewLayout header={<AdminViewHeader title="Reports" />}>
			<p className="text-muted-foreground text-sm">Nothing here yet.</p>
		</AdminViewLayout>
	);
}

export default page("reports", {
	component: ReportsPage,
	path: "/reports",
	label: "Reports",
});

Run questpie generate. The page is live at /admin/reports.

The component takes no props. Everything it needs comes from the admin hooks: useCollectionList, useCollectionItem, useCurrentUser, useBrand. They are the same hooks the built-in screens use.

The options

OptionWhat it does
componentThe React component, or an async function that imports one
pathThe URL under the admin base path. Required to route
labelThe sidebar label. Falls back to the formatted page name
iconA React component. An icon name string is ignored here
groupCarried on the nav entry. Nothing in the sidebar reads it
orderCarried the same way, default 0. Nothing sorts by it
showInNavfalse keeps the page routable but out of the sidebar

No `path`, no route

The router only matches a page that declares path. Without one the file still registers and still generates. No URL reaches it.

Getting it into the sidebar

The server owns the sidebar, and its default lists collections and globals. A page is not in it. Add an item in config/admin.ts.

src/questpie/server/config/admin.ts
import { adminConfig } from "#questpie/factories";

export default adminConfig({
	sidebar: {
		sections: [{ id: "tools", title: "Tools" }],
		items: [
			{
				sectionId: "tools",
				type: "link",
				label: "Reports",
				href: "/admin/reports",
				icon: { type: "icon", props: { name: "ph:chart-bar" } },
			},
		],
	},
});

A link item takes the URL you give it, so write the full path including /admin. There is also a page item keyed by pageId, the camelCased file name. Prefer link. The page item builds its href from path alone. It never adds the admin base path, so the link misses the route it names.

Lazy loading

component also accepts a loader, so a heavy page can stay out of the first bundle. Write it as an async function. The router only calls a loader whose constructor is AsyncFunction, and renders anything else as a component.

export default page("reports", {
	component: async () => ({
		default: (await import("./reports-screen.js")).ReportsScreen,
	}),
	path: "/reports",
});

Next

Dashboard widgets is the smaller version: your React on a tile, arranged by the server.

On this page