Building AdminForge: A Zero-Config Admin Panel and AI Orchestration Layer for Next.js
The story of building AdminForge — an open-source framework that auto-generates admin dashboards, REST APIs, and AI agent interfaces from a single TypeScript config file.
Building AdminForge: A Zero-Config Admin Panel and AI Orchestration Layer for Next.js
Every time I started a new side project, I ran into the same problem. I'd have a working database schema, a few API routes, and then I'd need to build an admin panel. Again. The forms, the tables, the auth, the CRUD logic — all of it boilerplate I'd written a dozen times before.
I tried the existing solutions. Strapi was too opinionated and heavy. AdminJS felt dated. Retool was SaaS-only. Nothing quite fit the "I'm building a Next.js app and just need an admin dashboard" workflow.
So I built AdminForge.
The Core Idea
The premise is simple: define your data model once in TypeScript, and get everything else for free.
import { defineConfig, collection, fields } from "@adminforge/core";
export const config = defineConfig({
collections: [
collection({
name: "posts",
label: "Blog Posts",
fields: {
title: fields.text({ required: true }),
slug: fields.slug({ from: "title" }),
content: fields.richText(),
published: fields.boolean({ default: false }),
category: fields.relation({ to: "categories", type: "many-to-one" }),
},
}),
collection({
name: "categories",
label: "Categories",
fields: {
name: fields.text({ required: true }),
},
}),
],
});
From this single config file, AdminForge generates:
- A Prisma schema with proper relations, indexes, and types
- A REST API with full CRUD, pagination, search, and validation
- A admin dashboard with forms, tables, rich text editing, and media uploads
- An MCP server that lets AI agents interact with your data using natural language
No code generation. No boilerplate files to maintain. The config is the source of truth.
How It Works Under the Hood
The Field System
Every field type in AdminForge maps to three things: a database column type, a UI component, and a Zod validation schema. This triple-mapping is what makes the whole system work.
// A simplified look at how fields are defined internally
registerField("text", {
db: { type: "String", nullable: true },
ui: { component: "TextInput", props: {} },
validation: z.string().optional(),
});
When you write fields.text({ required: true }), AdminForge adjusts all three layers:
- Database:
String(non-nullable) - UI:
TextInputwith a required indicator - Validation:
z.string().min(1)
This means validation is always consistent between the client, the API, and the database. There's no way for invalid data to sneak through because the schema definition drives all three layers simultaneously.
Schema Generation
The Prisma schema isn't manually maintained — it's generated from the TypeScript config. This is handled by a codegen script that walks through each collection and maps field types to Prisma types:
// Simplified schema generation logic
function generatePrismaSchema(config: AdminForgeConfig): string {
let schema = `generator client {\n provider = "prisma-client-js"\n}\n\n`;
schema += `datasource db {\n provider = "sqlite"\n url = env("DATABASE_URL")\n}\n\n`;
for (const collection of config.collections) {
schema += `model ${collection.name} {\n`;
schema += ` id String @id @default(cuid())\n`;
schema += ` createdAt DateTime @default(now())\n`;
schema += ` updatedAt DateTime @updatedAt\n`;
for (const [name, field] of Object.entries(collection.fields)) {
schema += ` ${name} ${field.db.type}${field.db.nullable ? "?" : ""}\n`;
}
schema += `}\n\n`;
}
return schema;
}
Running npx adminforge migrate --push generates the schema, pushes it to the database, and generates the Prisma client — all in one command.
The API Layer
The API is built on Next.js App Router's catch-all routes. A single route handler at app/api/[...slug]/route.ts handles everything:
import { createAdminForgeApi } from "@adminforge/core/next";
import { config } from "@/adminforge";
import { db } from "@/lib/adminforge";
export const { GET, POST, PATCH, DELETE } = createAdminForgeApi({ config, db });
That one line gives you:
GET /api/posts— list with pagination (?page=1&pageSize=10&search=term)GET /api/posts/:id— get by IDPOST /api/posts— create with Zod validationPATCH /api/posts/:id— update with partial dataDELETE /api/posts/:id— delete
Under the hood, createAdminForgeApi uses a controller pattern that's aware of the schema. It knows which fields are required, which are relations, and what validation rules apply. The same controller is used whether the request comes from the admin UI, a frontend fetch, or an AI agent.
The Admin Dashboard
The UI is a single-page React app that reads the config via an internal _config endpoint and renders the appropriate components. It's built with:
- @tanstack/react-table for data tables with sorting, filtering, and pagination
- Tiptap for rich text editing (bold, italic, headings, lists, images, links, tables)
- Tailwind CSS for styling
The dashboard is mounted as a catch-all page:
// app/admin/[[...admin]]/page.tsx
import { AdminDashboard } from "@adminforge/core/ui";
export default function Dashboard() {
return <AdminDashboard />;
}
The AdminDashboard component inspects the URL, finds the matching collection, and renders the appropriate view — list view, create form, or edit form.
The AI Layer (MCP Server)
This is the part I'm most excited about, and honestly, it started as an experiment.
The Model Context Protocol (MCP) is a standard for connecting AI models to external tools. I realized that if AdminForge already knows the schema and has a CRUD API, it can expose that same functionality as MCP tools with almost no extra work.
The MCP server in @adminforge/ai exposes these tools:
get_form_schema— Returns the schema for a collection, including field types and validation ruleslist_records— Lists records with optional limitssearch_records— Keyword search across text fieldscreate_record— Creates a new record with validationupdate_record— Updates an existing recorddelete_record— Deletes a recordupload_media— Uploads files and returns the public URL
Agent Authentication
AI agents don't log in with a username and password. They use agent tokens — short-lived JWTs that are scoped to specific collections and actions:
// Generate a token scoped to specific operations
const token = generateAgentToken(
"ai-agent", // user ID
"editor", // role
["posts:create", "posts:read", "categories:read"], // scopes
600 // expires in 10 minutes
);
The token carries its own permissions. When the MCP server receives a request, it verifies the JWT, checks the scopes, and only allows the operations the token was issued for. No session state, no database lookups — just cryptographic verification.
The signing key is ADMINFORGE_SECRET, which needs to be set in both your .env file (for Next.js) and your .mcp.json (for the MCP server). This caught me off guard during development — the MCP server runs as a subprocess and doesn't inherit the parent shell's environment variables. Took me a while to figure out why it was crashing on startup with "ADMINFORGE_SECRET env var is required."
Local vs Remote Mode
The MCP server can run in two modes:
Local mode — the server connects directly to your database. Great for development.
npx adminforge-ai start --config ./adminforge.ts
Remote proxy mode — the server talks to a deployed AdminForge instance via REST. Great for production.
npx adminforge-ai start --api-url https://my-app.vercel.app --token <AGENT_TOKEN>
In both modes, the same RBAC rules from your config are enforced. The AI agent can only do what its token allows.
The Monorepo Structure
AdminForge is a monorepo with two packages:
packages/
adminforge/ → @adminforge/core (schema, API, UI, auth)
ai/ → @adminforge/ai (MCP server, CLI)
apps/
example/ → Demo Next.js app
@adminforge/core is the heavy lifter — it contains the field definitions, schema generator, API controllers, admin UI, and auth system. @adminforge/ai is a thin wrapper that uses the core package's API layer and adds MCP protocol support.
The build uses tsup for the packages and Turborepo for orchestration. Each package outputs ESM, CJS, and type declarations.
What I Learned
1. Schema-as-code is powerful
Defining your data model in TypeScript (instead of JSON or YAML) gives you autocomplete, type checking, and the ability to use functions and variables. It's a small thing, but it makes the developer experience significantly better.
2. The triple-mapping pattern scales
Having each field map to database, UI, and validation simultaneously is the core insight that makes everything else work. Adding a new field type (like image or date) means defining it once and getting it everywhere.
3. AI integration is easier than you think
If you already have a well-defined schema and a CRUD API, exposing it to AI agents is almost trivial. The hard part isn't the MCP protocol — it's the authentication and scoping. Agent tokens solved that cleanly.
4. Build-time vs runtime env vars will bite you
Next.js collects page data at build time. If your code throws at module load time (like checking for an env var), it'll crash your CI even if the var is set at runtime. Always defer env var checks to runtime with a getter function:
// Bad — throws at build time
const SECRET = process.env.ADMINFORGE_SECRET;
if (!SECRET) throw new Error("...");
// Good — throws at request time
function getSecret(): string {
const secret = process.env.ADMINFORGE_SECRET;
if (!secret) throw new Error("...");
return secret;
}
What's Next
AdminForge is still early, but it's already useful. I'm using it in my own projects to manage blog content, product catalogs, and configuration data.
Some things on the roadmap:
- Custom field types — register your own fields with custom UI components
- Webhooks — notify external systems when data changes
- Caching — Redis-backed caching for read-heavy workloads
- Multi-tenant — isolate data per tenant in a single deployment
If you're building a Next.js app and need an admin panel, give it a try:
npm install @adminforge/core
The whole point is that you shouldn't have to think about the admin panel. Define your schema, mount the routes, and get back to building the parts of your app that actually matter.
AdminForge is open source. Check it out on GitHub.