Browse documentation

Getting started

Add FlowPanel to an existing Next.js app and expose your first resource.

This tutorial adds a working /admin route to an existing application. You will initialize FlowPanel, expose one table or model, apply its migration, and start the admin.

Prefer to look around first? The ScrapeAI example shows resources, dashboards, roles, actions, and realtime updates in one application.

Before you start

RequirementSupported rangeNote
Node.js>=20Required by the CLI and runtime packages.
Next.js^16.3.0The CLI init and doctor commands enforce Next.js 16.3 or newer because the generated App Router wiring depends on that baseline.
React^19.0.0React DOM uses the same major version.
Drizzle ORM>=0.45.2 <1.0.0Required only when using the Drizzle adapter.
Prisma Client>=5.0.0 <7.0.0Required only when using the Prisma adapter.

You need a Next.js App Router project and either Drizzle or Prisma already configured. FlowPanel ships its default admin CSS precompiled, so Tailwind is optional. Run the commands below from the project root. The Pages Router is not supported.

FlowPanel uses your existing database client and authentication. If the CLI cannot find a getSession helper, it creates a closed adapter: connect your provider and map auth.role before accessing the admin. Async role lookup is supported. An open development-only identity requires explicit --dev-auth and refuses production use.

Initialize FlowPanel

pnpm dlx @flowpanel/cli init

The CLI detects the package manager, ORM, app directory, path alias, database client, schema, auth helper, and installed Tailwind version when present. Confirm the detected paths when prompted.

It validates local modules and named exports, then installs @flowpanel/kit and a version-aligned local @flowpanel/cli. The root layout is preserved. Conflicting files stop the operation; a busy /admin is replaced by the free /flowpanel mount, or choose --path /ops/admin. To inspect the plan without writing or installing anything, run:

pnpm dlx @flowpanel/cli init --dry-run

Add your first resource

Open flowpanel.config.ts. The generated config has an empty resources array because FlowPanel cannot decide which application data belongs in the admin.

Use the version for your ORM and adjust the import paths and field names to match your project:

Adapter exampleToggle in the sidebar
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" },
    }),
  ],
});

Drizzle infers the row type from the table. A Prisma resource starts from a model name string, so the FlowpanelResources augmentation binds that name to the generated Prisma type. This is what makes columns, search fields, actions, and renderers type-safe.

Keep the generated auth block if it already matches your application. The example requires the admin role; a request without that role receives a server-side access denial, not merely a hidden navigation item.

Apply the migration

pnpm flowpanel migrate

The command reads DATABASE_URL, applies pending SQL files in flowpanel/migrations, and records them in FlowPanel's migration table. Use --dry-run to list pending files without executing them.

The initial migration is PostgreSQL SQL. Review or adapt it before running against MySQL or SQLite. The audit table remains unused until you configure an audit.sink.

Start the admin

pnpm flowpanel dev

Open http://localhost:3000/admin. You should see the resource in the navigation and a paginated list at its resource URL. If the navigation is empty, confirm that resources contains the resource and restart the dev process so the config is reloaded.

flowpanel dev starts Next.js and, when REDIS_URL is set and the project has a scripts/board-server.mts (or .ts) file, the BullMQ board process. init does not create that script; see the queues guide. Use your normal pnpm dev command if you do not need the board integration.

What the CLI created

The exact paths follow your app or src/app layout:

FilePurposeYou edit it when…
flowpanel.config.tsResources, dashboards, auth, scope, realtime, and themeYou change admin behavior
app/admin/[[...slug]]/page.tsxCatch-all admin pageYou change the mount path or eject page behavior
app/api/flowpanel/[...route]/route.tsResource and action handlersYou change the API mount path
app/api/flowpanel/stream/route.tsAuthenticated SSE endpointYou change the stream mount path
styles/admin.cssThin import of FlowPanel's precompiled, isolated stylesheetYou add safe local overrides
flowpanel/migrations/0001_init.sqlFlowPanel-owned database tablesYou review dialect-specific SQL

See Project structure for ownership and generated-file details.

Choose what to build next