Dashboard Pages
Add fully dynamic pages to the web dashboard from your extension: pages, React components, and server actions.
The dashboard is a separate Next.js app that Kora builds and runs for you. Extensions add to it entirely
from their own folder: a page declares a nav entry, a component is the React it renders, and a
route is a server action the component calls. Kora copies your dashboard/components and
dashboard/routes into the dashboard at build time and wires each page to its component through a
generated registry, so you never edit the dashboard itself.
The dashboard is a paid add-on. Extension pages only appear when the running instance has the Dashboard add-on and the dashboard has been built. See the Dashboard Setup guide.
Pages
A page is a small metadata class (loaded by Kora, served to the dashboard as nav). It points at a
component by name. Pages load from every page.js at any depth under src/dashboard/pages/.
import { Pages } from "#kora/extensions";
export class Products extends Pages {
constructor(kora) {
super(kora, {
name: "Products", // nav label
path: "/products", // dashboard URL
icon: "package", // a Tabler icon name
order: 2, // position in the sidebar
component: "products", // the component file to render
config: {} // arbitrary props passed to the component
});
}
}component is the base filename of a file in src/dashboard/components. config is handed to that
component as a prop, so one component can back several pages with different settings.
Components
A component is a normal React (TSX) file. It must default-export the component the page renders. It
receives the page (including its config) as a prop.
import { Stack, Title, Text } from "@mantine/core";
import type { PageMeta } from "@/lib/kora";
import { listProducts } from "../routes/products";
export default async function Products({ page }: { page: PageMeta }) {
const products = await listProducts();
return (
<Stack gap="lg">
<Title order={2}>{page.name}</Title>
{products.map(product => (
<Text key={product.name}>{product.name}</Text>
))}
</Stack>
);
}Two import rules keep components portable once they are copied into the dashboard:
- Shared dashboard building blocks (the Kora HTTP client, layout helpers, shared UI) come from the
dashboard itself via the
@/alias, for example@/lib/kora,@/components/codeBlock,@/actions/util. - Your own components and routes are referenced relatively, for example
../routes/productsor./productsTable.
Only the file named by a page needs a default export. Supporting components it imports (tables, modals, and so on) can live alongside it and use ordinary named exports.
Routes
Dashboard routes are server actions: server-side functions your components call to read or change data, usually by talking to your extension's HTTP API through the shared Kora client.
"use server";
import { kora } from "@/lib/kora";
import { perform } from "@/actions/util";
export async function listProducts() {
return kora.products.list();
}
export async function createProduct(name: string, price: number) {
return perform(() => kora.products.create(name, { price }));
}Because components and routes are copied into the dashboard when it builds, changing them takes effect on
the next dashboard build. Kora detects changes to any dashboard/ file and rebuilds automatically on
startup; while developing, run with DASHBOARD.DEV_MODE and restart Kora to pick up edits.

