Configuration
Compose a typed FlowPanel config from resources, dashboards, pages, and runtime policies.
flowpanel.config.ts is the server-side definition of the admin. Keep it declarative at the top and move individual resources, dashboards, and policies into focused modules as the admin grows.
Start with the smallest config
The complete first-resource configs are compiled with the docs:
import { defineAdmin, resource } from "@flowpanel/kit";
import { drizzleAdapter } from "@flowpanel/kit/drizzle";
import { getSession } from "@/server/lib/auth";
import { db } from "@/server/lib/db";
import * as schema from "@/server/lib/db/schema";
export default defineAdmin({
adapter: drizzleAdapter({ db, schema }),
auth: {
session: getSession,
role: (session) => (session as { user?: { role?: string } } | null)?.user?.role ?? "guest",
requireRole: "admin",
},
resources: [
resource(schema.users, {
columns: ["email", "name", "role", "createdAt"],
search: ["email", "name"],
defaultSort: { field: "createdAt", dir: "desc" },
}),
],
});
import { defineAdmin, resource } from "@flowpanel/kit";
import { prismaAdapter } from "@flowpanel/kit/prisma";
import { getSession } from "@/server/lib/auth";
import { prisma, type User } from "@/server/lib/prisma";
declare module "@flowpanel/kit" {
interface FlowpanelResources {
User: User;
}
}
export default defineAdmin({
adapter: prismaAdapter({ prisma, provider: "postgresql" }),
auth: {
session: getSession,
role: (session) => (session as { user?: { role?: string } } | null)?.user?.role ?? "guest",
requireRole: "admin",
},
resources: [
resource("User", {
columns: ["email", "name", "role", "createdAt"],
search: ["email", "name"],
defaultSort: { field: "createdAt", dir: "desc" },
}),
],
});
The adapter and auth are server-only. resources, dashboards, and pages describe what FlowPanel exposes. Features omitted from config remain unavailable.
One resource per file
Large inline arrays become difficult to review. Export each resource from a normal TypeScript module and compose them in the root config:
// src/admin/resources/users.ts
import { resource } from "@flowpanel/kit";
import { users } from "@/server/lib/db/schema";
export const usersResource = resource(users, {
columns: ["email", "name", "role", "createdAt"],
search: ["email", "name"],
});// flowpanel.config.ts
export default defineAdmin({
adapter: drizzleAdapter({ db, schema }),
auth,
resources: [usersResource, ordersResource],
dashboards: [overviewDashboard],
});Use the root file for composition, not a second configuration abstraction. A reader should be able to see every registered surface from its imports and arrays.
Type database contexts
Augment FlowpanelTypes once so ctx.db in actions, widgets, filters, and policy callbacks is your actual client:
declare module "@flowpanel/kit" {
interface FlowpanelTypes {
db: typeof db;
}
}Drizzle resources infer rows from table objects. Prisma and string-named custom resources use FlowpanelResources; see Type inference and registries.
Keep policies close to their boundary
Admin-wide auth and tenant resolution belong at the root. Resource access, field policies, action roles, and per-resource scope belong beside the resource they protect. Hiding navigation or a button is never the security boundary—the generated handlers repeat these checks on the server.
Cross-origin write protection is enabled by default. Configure security.trustedOrigins only when a trusted origin must submit writes; do not use it as authentication.
Reload changes
The config is evaluated on the server. Restart the dev process after changing module augmentation, adapter setup, or environment-dependent initialization. Ordinary config edits are normally picked up by Next.js development reload.
Next, add list behavior in Resources. The exhaustive generated shape is in Configuration reference.