| name | admin-workflows |
| description | Implement and manage admin workflows in Next.js systems with enterprise security. Use when building admin portals, CRUD operations, secure APIs, form handling, and administrative task automation. |
Admin Workflows
A comprehensive skill for implementing secure, scalable admin workflows in Next.js 16 + React 19 App Router systems, following Fortune-500 security standards with RBAC, CSRF protection, tenant isolation, and comprehensive audit logging.
Quick Start
Create a basic admin CRUD workflow:
API Route (
app/api/admin/resource/route.ts):import { adminRead, adminMutation } from "@/lib/admin/route"; import { prisma } from "@/lib/prisma"; import * as z from "zod"; const schema = z.object({ name: z.string().min(1) }); export async function GET() { return adminRead(req, { permissions: ["resource.read"] }, async (user) => { return await prisma.resource.findMany({ where: { tenantId: user.tenantId } }); }); } export async function POST(req: NextRequest) { return adminMutation(req, { permissions: ["resource.write"] }, async (user, body) => { const data = schema.parse(body); return await prisma.resource.create({ data: { ...data, tenantId: user.tenantId } }); }); }Admin Page (
app/(admin)/admin/resource/page.tsx):export default function ResourcePage() { return <ResourceList />; }Form Component (
components/admin/resource/resource-form.tsx):"use client"; import { useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; import { fetchWithCsrf } from "@/lib/fetchWithCsrf"; export function ResourceForm() { const form = useForm({ resolver: zodResolver(schema) }); const onSubmit = async (data) => { await fetchWithCsrf("/api/admin/resource", { method: "POST", body: JSON.stringify(data) }); }; return <Form {...form}><form onSubmit={form.handleSubmit(onSubmit)}>{/* fields */}</form></Form>; }
Core Concepts
Admin API Patterns
adminRead: Standardized GET operations with permission checks and tenant scopingadminMutation: POST/PUT/DELETE with CSRF, RBAC, audit logging, and security validation- Tenant Isolation: Automatic data filtering by
tenantIdto prevent cross-tenant access - Safe DTOs: Predefined selects like
SAFE_USER_WITH_ROLES_SELECTfor data protection
Security Architecture
- Server Control Plane: All security enforcement happens server-side; client checks are UI-only
- RBAC Permissions: Granular permissions like
"users.read","contracts.write" - CSRF Protection: Same-origin enforcement using
assertSameOrigin - Audit Logging: Every privileged mutation tracked with
createAuditLog
Form Handling
- React Hook Form + Zod: Type-safe validation with
zodResolver - Permission Checks:
useAdmin().hasPermission()for UI gating - CSRF-Aware Fetch:
fetchWithCsrf()for secure API calls - Multi-Step Forms: Progressive validation with step-based workflows
Workflows
1. Implementing Admin CRUD APIs
Purpose: Create secure REST endpoints for admin data operations.
Steps:
- Define Zod schema for input validation
- Use
adminReadfor GET with permission and tenant checks - Use
adminMutationfor writes with CSRF and audit - Apply
tenantWhere(user)for data scoping - Return safe data using predefined selects
Example: User Management API
import { adminRead, adminMutation } from "@/lib/admin/route";
import { prisma } from "@/lib/prisma";
import { tenantWhere } from "@/lib/admin/guards";
import { createAuditLog } from "@/lib/admin/audit";
import * as z from "zod";
const createUserSchema = z.object({
name: z.string().min(1),
email: z.string().email(),
password: z.string().min(8),
roleIds: z.array(z.string()).optional(),
});
export async function GET() {
return adminRead(req, { permissions: ["users.read"] }, async (user) => {
const users = await prisma.user.findMany({
where: { ...tenantWhere(user), deletedAt: null },
select: SAFE_USER_WITH_ROLES_SELECT,
});
return users;
});
}
export async function POST(req: NextRequest) {
return adminMutation(req, { permissions: ["users.write"] }, async (user, body) => {
const validatedData = createUserSchema.parse(body);
const { roleIds, password, ...userData } = validatedData;
const passwordHash = await bcrypt.hash(password, 10);
const newUser = await prisma.user.create({
data: {
...userData,
passwordHash,
tenantId: user.tenantId,
RoleAssignment: roleIds ? {
create: roleIds.map(roleId => ({ Role: { connect: { id: roleId } } })),
} : undefined,
},
select: SAFE_USER_WITH_ROLES_SELECT,
});
await createAuditLog({
action: "create_user",
resource: "user",
resourceId: newUser.id,
actorId: user.id,
actorEmail: user.email,
after: newUser,
});
return newUser;
});
}
2. Building Admin Forms
Purpose: Create user-friendly forms with security and validation.
Steps:
- Use
useFormwithzodResolverfor validation - Check permissions with
useAdminhook - Use
fetchWithCsrffor API submissions - Handle loading states and show toast notifications
- Navigate on success with
router.push()androuter.refresh()
Example: User Creation Form
"use client";
import { useState } from "react";
import { useRouter } from "next/navigation";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
import { toast } from "sonner";
import { fetchWithCsrf } from "@/lib/fetchWithCsrf";
import { useAdmin } from "@/hooks/useAdmin";
const userSchema = z.object({
name: z.string().min(1, "Name is required"),
email: z.string().email("Invalid email"),
roleIds: z.array(z.string()),
});
type UserFormValues = z.infer<typeof userSchema>;
interface UserFormProps {
roles: { id: string; name: string }[];
}
export function UserForm({ roles }: UserFormProps) {
const router = useRouter();
const { hasPermission } = useAdmin();
const [loading, setLoading] = useState(false);
const form = useForm<UserFormValues>({
resolver: zodResolver(userSchema),
defaultValues: { name: "", email: "", roleIds: [] },
});
const onSubmit = async (data: UserFormValues) => {
try {
setLoading(true);
const res = await fetchWithCsrf("/api/admin/users", {
method: "POST",
body: JSON.stringify(data),
});
if (!res.ok) throw new Error("Failed to create");
toast.success("User created");
router.refresh();
router.push("/admin/users");
} catch (error) {
toast.error("Something went wrong");
} finally {
setLoading(false);
}
};
return (
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8">
<FormField
control={form.control}
name="name"
render={({ field }) => (
<FormItem>
<FormLabel>Name</FormLabel>
<FormControl>
<Input placeholder="John Doe" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="email"
render={({ field }) => (
<FormItem>
<FormLabel>Email</FormLabel>
<FormControl>
<Input placeholder="john@example.com" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<Button type="submit" disabled={loading || !hasPermission("users.create")}>
{loading ? "Creating..." : "Create User"}
</Button>
</form>
</Form>
);
}
3. Multi-Step Admin Forms
Purpose: Handle complex forms with progressive validation.
Steps:
- Define steps with validation groups
- Use
form.trigger()for step validation - Show progress indicators
- Validate all steps before final submission
Example: Contract Creation Form
const steps = [
{ id: 1, name: "Basics", fields: ["title", "tenantId", "status", "value"] },
{ id: 2, name: "Terms", fields: ["startDate", "endDate"] },
{ id: 3, name: "Content", fields: ["content"] },
];
export function ContractForm({ tenants }: ContractFormProps) {
const [currentStep, setCurrentStep] = useState(1);
const form = useForm({ resolver: zodResolver(contractSchema) });
const nextStep = async () => {
const stepFields = steps.find(s => s.id === currentStep)?.fields;
const isValid = await form.trigger(stepFields as any);
if (isValid) setCurrentStep(prev => prev + 1);
};
const onSubmit = async (data) => {
const payload = { ...data, startDate: new Date(data.startDate).toISOString() };
await fetchWithCsrf("/api/admin/contracts", {
method: "POST",
body: JSON.stringify(payload),
});
};
return (
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)}>
{/* Step indicator */}
{currentStep === 1 && /* Basics fields */}
{currentStep === 2 && /* Terms fields */}
{currentStep === 3 && /* Content fields */}
<div className="flex justify-between">
{currentStep > 1 && <Button onClick={() => setCurrentStep(prev => prev - 1)}>Previous</Button>}
{currentStep < steps.length ? (
<Button onClick={nextStep}>Next</Button>
) : (
<Button type="submit">Create Contract</Button>
)}
</div>
</form>
</Form>
);
}
Advanced Features
Audit Logging Integration
Always include audit events for privileged mutations:
await createAuditLog({
action: "create_user",
resource: "user",
resourceId: newUser.id,
actorId: user.id,
actorEmail: user.email,
before: null, // for creates
after: newUser, // sanitized data
});
Password Security
Use enhanced validation for user creation:
import { validatePasswordEnhanced } from "@/lib/security/password-enhanced";
const passwordError = await validatePasswordEnhanced(password);
if (passwordError) {
return jsonNoStore({ error: passwordError }, { status: 400 });
}
Tenant Scoping
Always scope queries by tenant:
const where = {
...tenantWhere(user), // Adds tenantId filter
deletedAt: null, // Soft delete filter
};
Troubleshooting
Common Issues
Permission Denied: Check that the user has the required permissions in their role assignments.
CSRF Errors: Ensure using fetchWithCsrf on the client and adminMutation on the server.
Tenant Access: Verify tenantWhere(user) is applied to all queries.
Audit Failures: Ensure audit logs are created for all mutations with proper redaction.
Best Practices
- Always use
adminRead/adminMutationinstead of direct Prisma calls - Validate all inputs with Zod schemas
- Include audit logging for any data changes
- Use safe DTOs to prevent sensitive data exposure
- Test permissions thoroughly in different user contexts
- Handle soft deletes with
deletedAt: nullfilters
Examples
See the codebase for complete implementations:
- `app/api/admin/users/route.ts` - User CRUD API
- `app/api/admin/assignments/route.ts` - Assignment management
- `components/admin/users/user-form.tsx` - User form component
- `components/admin/contracts/contract-form.tsx` - Multi-step contract form