@vijay_j/next-admin
v1.0.1
Published
Plug-and-Play Admin UI for Modern Next.js Backends
Maintainers
Readme
@vijay_j/next-admin
Plug-and-Play Admin UI for Modern Next.js Backends
A drop-in admin panel optimized for Next.js App Router with minimal configuration and opinionated defaults.
Features
- 🚀 Zero-config first - Works out of the box with sensible defaults
- 📦 Prisma-first - First-class Prisma integration with schema introspection
- 🔌 REST API support - Flexible adapter for any REST backend
- 🔐 Built-in auth - NextAuth, Clerk, and Supabase support
- 🛡️ Permissions - Role-based access control at model and field level
- 📊 Smart CRUD - Auto-generated list, detail, create, and edit views
- 🎨 Beautiful UI - Clean, responsive design with dark mode
- ⚡ Type-safe - Full TypeScript support
Installation
npm install @vijay_j/next-admin
# or
yarn add @vijay_j/next-admin
# or
pnpm add @vijay_j/next-adminQuick Start
Initialize (Optional CLI)
npx @vijay_j/next-admin initThis will:
- Detect your Next.js App Router
- Detect your Prisma schema (if present)
- Create
/adminroute with layout and auth guard - Auto-register your models
- Generate CRUD pages for each model
Visit Your Admin
npm run dev
# Open http://localhost:3000/adminConfiguration
Basic Setup
// app/admin/admin.config.ts
import { defineAdmin } from '@vijay_j/next-admin';
export const adminConfig = defineAdmin({
title: 'My Admin Panel',
basePath: '/admin',
auth: {
provider: 'nextauth', // or 'clerk', 'supabase'
loginUrl: '/login',
},
models: {
User: {
label: 'User',
labelPlural: 'Users',
icon: 'Users',
fields: {
id: { hidden: true },
email: { type: 'email', searchable: true },
name: { searchable: true },
role: { type: 'enum', filterable: true, options: [
{ label: 'Admin', value: 'admin' },
{ label: 'User', value: 'user' },
]},
createdAt: { type: 'datetime', readonly: true },
},
permissions: {
read: ['admin', 'support'],
create: ['admin'],
update: ['admin'],
delete: ['admin'],
},
},
},
});Field Types
| Type | Description |
|------|-------------|
| string | Text input |
| number | Number input |
| boolean | Toggle/checkbox |
| datetime | Date and time picker |
| date | Date picker |
| email | Email input with validation |
| password | Password input with toggle |
| url | URL input |
| textarea | Multi-line text |
| richtext | Rich text editor |
| json | JSON editor |
| enum | Select dropdown |
| relation | Related model picker |
| file | File upload |
| image | Image upload with preview |
Field Configuration
fields: {
name: {
label: 'Full Name', // Display label
description: 'Enter name', // Help text
placeholder: 'John Doe', // Input placeholder
required: true, // Validation
readonly: false, // Can edit?
hidden: false, // Hide everywhere
hiddenInList: false, // Hide in table
hiddenInDetail: false, // Hide in detail view
hiddenInForm: false, // Hide in forms
searchable: true, // Include in search
filterable: true, // Show filter option
sortable: true, // Allow sorting
width: 200, // Column width
// For enum type
options: [
{ label: 'Active', value: 'active' },
{ label: 'Inactive', value: 'inactive' },
],
// For relation type
relation: {
model: 'Organization',
displayField: 'name',
valueField: 'id',
},
// Custom rendering
renderList: (value, record) => <Badge>{value}</Badge>,
renderDetail: (value, record) => <div>{value}</div>,
// Custom validation
validation: z.string().min(2).max(100),
},
}Authentication
NextAuth
// app/admin/layout.tsx
import { getServerSession } from 'next-auth';
import { redirect } from 'next/navigation';
import { AdminLayout } from 'next-admin/client';
import { adminConfig } from './admin.config';
import { authOptions } from '@/lib/auth';
export default async function Layout({ children }) {
const session = await getServerSession(authOptions);
if (!session) {
redirect('/api/auth/signin');
}
return (
<AdminLayout config={adminConfig} user={session.user}>
{children}
</AdminLayout>
);
}Clerk
import { auth, currentUser } from '@clerk/nextjs';
export default async function Layout({ children }) {
const { userId } = auth();
if (!userId) {
redirect('/sign-in');
}
const user = await currentUser();
return (
<AdminLayout
config={adminConfig}
user={{
id: user.id,
email: user.emailAddresses[0]?.emailAddress,
name: `${user.firstName} ${user.lastName}`,
roles: user.publicMetadata.roles || ['user'],
}}
>
{children}
</AdminLayout>
);
}Permissions
Model-Level Permissions
permissions: {
read: ['admin', 'support'], // Who can view
create: ['admin'], // Who can create
update: ['admin', 'support'], // Who can edit
delete: ['admin'], // Who can delete
}Field-Level Permissions
permissions: {
read: ['admin'],
write: ['admin'],
fields: {
salary: {
read: ['admin', 'hr'],
write: ['admin'],
},
ssn: {
read: ['admin'],
write: ['admin'],
},
},
}Super Admin
export const adminConfig = defineAdmin({
permissions: {
adminRoles: ['admin', 'support'],
superAdminRoles: ['super_admin'], // Bypasses all permissions
},
// ...
});Custom Actions
actions: [
{
id: 'ban-user',
label: 'Ban User',
icon: 'Ban',
variant: 'destructive',
confirmation: 'Are you sure you want to ban this user?',
roles: ['admin'],
handler: async (ids, context) => {
await banUsers(ids);
return { success: true, message: 'Users banned' };
},
},
{
id: 'send-email',
label: 'Send Email',
icon: 'Mail',
bulk: true, // Available for bulk selection
handler: async (ids) => {
await sendBulkEmail(ids);
return { success: true };
},
},
],Data Adapters
Prisma (Default)
import { createPrismaAdapter } from 'next-admin/adapters/prisma';
import { prisma } from '@/lib/prisma';
export const adminConfig = defineAdmin({
adapter: createPrismaAdapter({
prisma,
queryModifiers: {
User: {
include: { organization: true },
},
},
}),
// ...
});REST API
import { createRestAdapter } from 'next-admin/adapters/rest';
export const adminConfig = defineAdmin({
adapter: createRestAdapter({
baseUrl: 'https://api.example.com',
headers: {
'Authorization': `Bearer ${token}`,
},
endpoints: {
User: {
list: '/users',
get: '/users/{id}',
create: '/users',
update: '/users/{id}',
delete: '/users/{id}',
},
},
}),
// ...
});Navigation
navigation: [
{
title: 'Dashboard',
href: '/admin',
icon: 'LayoutDashboard',
},
{
title: 'Content',
items: [
{ title: 'Posts', href: '/admin/post', icon: 'FileText' },
{ title: 'Categories', href: '/admin/category', icon: 'Folder' },
],
},
{
title: 'Users',
href: '/admin/user',
icon: 'Users',
badge: '5',
badgeVariant: 'destructive',
roles: ['admin'], // Only visible to admins
},
],Audit Logging
audit: {
enabled: true,
models: ['User', 'Order'], // Empty = all models
handler: async (entry) => {
// Custom handler - save to database, send to service, etc.
await prisma.auditLog.create({ data: entry });
},
},Add the audit log table to your Prisma schema:
model AdminAuditLog {
id String @id @default(cuid())
timestamp DateTime @default(now())
userId String
userName String?
action String
model String
recordId String
changes Json?
metadata Json?
@@index([model, recordId])
@@index([userId])
@@index([timestamp])
}Customization
Custom Components
// Override list view for a model
User: {
listComponent: CustomUserList,
detailComponent: CustomUserDetail,
formComponent: CustomUserForm,
}
// Or use custom field renderers
fields: {
status: {
renderList: (value) => (
<Badge variant={value === 'active' ? 'success' : 'secondary'}>
{value}
</Badge>
),
},
}Theme
theme: {
primaryColor: 'blue',
darkMode: true,
defaultColorScheme: 'system',
logo: '/logo.svg', // or: logo: CustomLogoComponent
cssVariables: {
'--primary': '221.2 83.2% 53.3%',
},
},Environment Badge
environment: process.env.NODE_ENV === 'production'
? 'production'
: 'development',CLI Commands
# Initialize admin panel
npx @vijay_j/next-admin init
# Generate pages for specific models
npx @vijay_j/next-admin generate --models User,Order
# Generate for all models
npx @vijay_j/next-admin generate --all
# Add a new model
npx @vijay_j/next-admin add-model Product
npx @vijay_j/next-admin add-model Product --rest # Using REST adapterExample Application
A complete example application is included in the example/ directory. To run it:
# Navigate to example directory
cd example
# Install dependencies
npm install
# Generate Prisma client and run migrations
npx prisma generate
npx prisma migrate dev
# Seed the database with sample data
npm run seed
# Start the development server
npm run dev
# Open http://localhost:3000Test Accounts
| Email | Password | Role | |-------|----------|------| | [email protected] | admin123 | ADMIN | | [email protected] | super123 | SUPER_ADMIN | | [email protected] | editor123 | EDITOR | | [email protected] | password123 | USER |
Features Demonstrated
- Dashboard - Stats overview with recent orders
- User Management - CRUD with role management
- Posts - Content management with categories and tags
- Products - E-commerce product management
- Orders - Order management with status updates
- Settings - System configuration
- Audit Log - Track all changes
Production Checklist
- [ ] Set up proper authentication
- [ ] Configure role-based permissions
- [ ] Enable audit logging
- [ ] Set environment badge
- [ ] Configure proper CORS for API routes
- [ ] Enable rate limiting on admin routes
- [ ] Set up proper error monitoring
- [ ] Test all CRUD operations
- [ ] Review field visibility settings
- [ ] Test with different user roles
TypeScript Support
Full TypeScript support is included:
import type { AdminConfig, AdminUser, ModelConfig, FieldConfig } from '@vijay_j/next-admin';Contributing
Contributions are welcome! Please open an issue or submit a pull request.
License
MIT © vijay_j
