Universal, modern, performant admin panel framework — inspired by AdminJS and built on a contemporary stack with a focus on DX, customization, persistent system stores, and production-grade caching.
Modern Admin is an independent project, not affiliated with or endorsed by AdminJS / SoftwareBrothers.
AdminJS pioneered an elegant adapter/decorator model for auto-generating CRUD admin panels from ORM schemas. Modern Admin keeps that model but addresses its weaknesses:
- Outdated UI library and styling story → shadcn/ui + Tailwind 4
- Weak component customization, slow runtime bundling → Vite + dynamic imports, no runtime bundler
- No built-in caching → Redis on the backend (
@modern-admin/cache-redis), in-processMemoryCacheProviderin core, TanStack Query on the frontend; per-resource TTL config, splitlist/recordtag invalidation, in-flight request deduplication - No GraphQL → REST + GraphQL (queries, mutations, DataLoader, uploads) in parallel over the same decorated resources
- Limited auth → Better Auth (OAuth, passkeys, 2FA, magic links, API keys)
- No realtime → NestJS WebSocket gateway with Redis pub/sub
- No persistent ops surface → system-prisma / system-drizzle packages with pre-built models for action logs, history, webhooks, AI tasks, config, cache
- No background work →
@modern-admin/queue(BullMQ + cron + distributed locks)
| Layer | Choice |
|---|---|
| Runtime / pm | Bun |
| Frontend | Vite 8 + React 19 + TanStack Router 1.x |
| UI | shadcn/ui, Tailwind CSS 4 (CSS-first), Recharts 3 |
| Backend | NestJS 12+ (REST + GraphQL + WebSocket + OpenAPI) |
| ORMs | Prisma 7+, Drizzle 0.45+ |
| Auth | Better Auth 1.7+ (cookies + API keys) |
| Cache | Redis (backend) + TanStack Query 5 (frontend) |
| Queue | BullMQ + @nestjs/bullmq (jobs, cron, webhooks) |
| Validation | Zod 4 end-to-end |
| Language | TypeScript 7 (strict) |
Dependency policy: this project always pins to the latest stable release of each library. Code is adapted for breaking changes, not held back.
modern-admin/
├── apps/
│ ├── _shared/ — shared admin config for the reference apps
│ ├── api-prisma/ — NestJS reference app (Postgres + Prisma 7)
│ ├── web/ — Vite + React 19 reference SPA
│ └── e2e/ — Playwright e2e tests
├── packages/
│ ├── core/ — adapters, decorators, actions, ports, system subsystems
│ ├── nest/ — REST controllers + OpenAPI + WS bootstrap
│ ├── graphql/ — schema builder (queries + mutations + DataLoader + uploads)
│ ├── realtime/ — WebSocket gateway, Redis pub/sub
│ ├── queue/ — BullMQ module + cron decorator + distributed locks
│ ├── react/ — components, hooks, AdminClient, dashboard
│ ├── ui/ — 40+ i18n-unaware shadcn primitives + charts + themes
│ ├── web/ — pre-built React SPA (mountable or standalone)
│ ├── i18n/ — 9 locales (~480 keys each)
│ ├── adapter-prisma/ — Prisma 7 adapter
│ ├── adapter-drizzle/ — Drizzle 0.45 adapter
│ ├── auth-better-auth/ — Better Auth integration
│ ├── cache-redis/ — Redis cache + pub/sub invalidation
│ ├── system-prisma/ — persistent system stores (logs/history/webhooks/AI/config/cache)
│ ├── system-drizzle/ — same for Drizzle
│ ├── feature-upload/ — Local + S3 file uploads, busboy, multipart
│ ├── feature-history/ — revision history + field diff
│ ├── feature-m2m/ — many-to-many junction tables
│ ├── feature-password/ — argon2/bcrypt password hashing
│ ├── feature-json-by-key/ — declarative JSON sub-properties
│ ├── license/ — license-gate (jose, Ed25519/JWS) for Pro packages
│ ├── telemetry/ — anonymous usage telemetry
│ ├── create/ — scaffold, schema-generation, and custom-UI CLI
│ └── tsconfig/ — shared TS presets
├── .changeset/ — Changesets workflow
├── .github/workflows/ — CI, release preparation, protected npm publish
├── scripts/ — dev.sh orchestrator, release.ts
├── CONTRIBUTING.md — contribution and branch workflow
├── RELEASING.md — full release procedure
├── docker-compose.yml — Postgres + Redis for development
└── package.json — bun workspaces root
┌─────────────────────────────────┐
│ Frontend │
│ Vite + React 19 + shadcn/ui │
│ TanStack Query + Recharts │
└──────────────┬──────────────────┘
│ HTTP / GraphQL / WS
┌──────────────▼──────────────────┐
│ @modern-admin/nest │
│ REST · GraphQL · WS · OpenAPI │
│ Auth Guard · Cache · Queue │
└──────────────┬──────────────────┘
│
┌──────────────▼──────────────────┐
│ @modern-admin/core │
│ ModernAdmin · ResourcesFactory │
│ Decorators · Actions · Filter │
│ Ports · Subsystems · Dashboard │
└────┬─────────┬──────────┬───────┘
│ │ │
┌────────────────────▼─┐ ┌─────▼──────┐ ┌─▼──────────────┐
│ adapter-{prisma, │ │ system- │ │ feature-* │
│ drizzle} │ │ {prisma, │ │ (upload, │
│ auth-better-auth │ │ drizzle} │ │ logging, │
│ cache-redis │ │ │ │ history, …) │
└──────────────────────┘ └────────────┘ └────────────────┘
Core defines abstractions (BaseDatabase, BaseResource, BaseProperty,
BaseRecord), Zod-validated decorator options, an action system with
before/after hooks, and ports for auth/cache/realtime/components,
plus subsystem stores (action logs, history, webhooks, AI tasks,
config, SQL cache) shared by feature plugins. Core also ships a
MemoryCacheProvider (in-process, TTL + tag-index) and NoopCacheProvider
for zero-config dev/test use. Adapters, system packages, and transports
plug in without leaking ORM- or framework-specific types into core.
Two scopes — both transform ResourceOptions:
-
Local
FeatureFn— per-resource, declared inResourceWithOptions.features. Example:uploadFeature,actionLoggingFeature,historyFeature,passwordsFeature,m2mFeature,jsonByKeyFeature,aiFillFeature. -
Global
GlobalPlugin— process-wide, registered once inModernAdmin({ plugins: [...] }). Example:actionLoggingPlugin,historyPlugin,webhookPlugin.
All plugins chain hooks (never overwrite) and any port the plugin
needs (ILogStore, IHistoryStore, IWebhookStore, IAiTaskStore, …)
has an in-memory default plus a real Prisma / Drizzle implementation
in the system-* packages.
Custom actions support an optional guard field — a confirmation
prompt shown before the action fires. The confirmGuard(action, dialogs)
helper in @modern-admin/react wires this across every invoke call-site
(toolbar, bulk bar, row dropdown, show-page). Translations for
relatedResources tab labels are now part of metadataTranslations
(key = resource id), resolved via localizeRelatedResources().
The sidebar supports a configurable option to show resource IDs alongside resource names, useful during development.
Prerequisites: Bun ≥ 1.4, Node 20+, Docker for the dev databases.
# Install all workspaces
bun install
# Bring up Postgres + Redis
bun run docker:up
# Run the reference apps (separate terminals)
bun run dev:api # NestJS API — Postgres + Prisma 7 (apps/api-prisma)
bun run dev:web # Vite + React 19 SPA
# Workspace-wide checks
bun run typecheck # all workspace projects
bun test # 547 unit tests, all green
bun run e2e # Playwright (PLAYWRIGHT_CHANNEL=chrome on Ubuntu 26.04)Reference API listens on http://localhost:3001 (/health for a smoke check).
Reference web app on http://localhost:3000 (override with WEB_PORT).
Scaffold a new project:
bun create @modern-admin my-adminExtend an existing host project:
bunx @modern-admin/create generate # add ma_* system tables
bunx @modern-admin/create setup-ui # create and connect a custom UI bundleFull documentation lives at https://docs.modernadminpro.com/docs/getting-started.
- Validation is Zod everywhere — option schemas, DTOs, form resolvers.
- Identifier policy: UUID v7 everywhere via
uuidv7()from@modern-admin/core— nevercrypto.randomUUID(),nanoid, or ORM defaults. - Tests live next to packages in
<pkg>/test/and run withbun test. - Workspace test sweep:
bun --filter '*' test(e2e suite is opted out because it uses non-testscript names). - Tailwind 4 uses CSS-first config (
@theme,@import "tailwindcss"); there is notailwind.config.js. Each package scans its own sources from its ownstyles.cssand composes upward via@import "@modern-admin/<pkg>/styles.css"— never@sourceacross package boundaries, since that path breaks under a non-hoisted node_modules layout. Apps import@modern-admin/react/styles.cssand add one@sourcefor their own files.borderrequires an explicit color in Tailwind 4 — pair withborder-border. - TypeScript 7 (native compiler) carries the stricter TypeScript 6 checks
forward: use
as unknown as Tfor variance/abstract constructor casts. The sametschandles both--noEmittypecheck and-pbuild emit, but tooling that needs the TS JavaScript API (unplugin-dts, in the web build) uses the side-by-side@typescript/typescript6dev dependency — keep it installed. - React 19: use
import type { ReactElement } from 'react'instead ofJSX.Element. - Mobile-first UI: base classes target small viewports,
sm:/md:/lg:enhance progressively. Verify any new screen at ~375px width. - i18n is mandatory — no hardcoded user-visible strings. UI components are
i18n-unaware (accept
labels?prop),@modern-admin/reactis the translation boundary. - Action buttons get a leading
lucide-reacticon when semantics map cleanly (e.g.Plus=create,Trash2=delete,Pencil=edit,Eye=view). - Custom actions support
guard?: string— a description shown in a confirm dialog before the action fires. UseconfirmGuard(action, dialogs)from@modern-admin/reactat every invoke call-site. -
relatedResources[].labelis translatable: setrelatedResourcesmap inmetadataTranslations;localizeRelatedResources()resolves the labels. - Cache behavior is configurable per resource via
ResourceOptions.cache:{ list/show/search/http?: { enabled, ttl, jitterRatio, crossReplicaLock } }. Core ships bounded-LRUMemoryCacheProviderand the defaultNoopCacheProvider; Redis is in@modern-admin/cache-redis. HTTP entries are scoped per principal, invalidated with role-permission changes, and bypassed when functional action/propertyisAccessiblerules make a cached filtered response unsafe to replay. Action and HTTP caches share versioned keys, tag-generation fencing, targeted invalidation, positive TTL jitter, fail-open reads/writes, and invalidation quarantine. - Commit messages follow Angular Conventional Commits:
<type>(<scope>): <subject>with a per-package body. - Contributions merge into
develop; prepared releases are promoted tomainand published through Changesets + GitHub Actions. SeeCONTRIBUTING.mdandRELEASING.md.
MIT. Every published @modern-admin/* package declares
"license": "MIT" in its package.json; the canonical text lives in
LICENSE at the repo root.