Nuxt Crouton provides a three-tier admin architecture with clear separation of concerns:
| Tier | Route Pattern | Purpose | Access |
|---|---|---|---|
| User | /dashboard/[team]/* | User-facing features | Any team member |
| Admin | /admin/[team]/* | Team management | Team admins/owners |
| Super Admin | /super-admin/* | System management | App owner only |
@fyit/crouton-admin is a separate package, but it is automatically included when you install the @fyit/crouton meta-package. You do not need to add it to your extends array manually.The admin functionality is included automatically via the @fyit/crouton meta-package:
pnpm add @fyit/crouton
export default defineNuxtConfig({
extends: ['@fyit/crouton'], // Includes auth, admin, i18n
croutonAuth: {
teams: {
allowCreate: true,
showSwitcher: true
}
}
})
View key metrics at a glance:
Full user lifecycle management:
View and monitor all teams:
Debug issues by becoming any user:
/dashboard/[team]/*)Routes for regular team members:
| Page | Path | Purpose |
|---|---|---|
| Team Selection | /dashboard | Select which team to access |
| User Dashboard | /dashboard/[team] | User home/overview |
| Profile | /dashboard/[team]/profile | My profile settings |
| Settings | /dashboard/[team]/settings | My preferences |
| App Routes | /dashboard/[team]/{app} | Auto-discovered from apps |
/admin/[team]/*)Routes for team administrators:
| Page | Path | Purpose |
|---|---|---|
| Admin Dashboard | /admin/[team] | Team admin overview |
| Collections | /admin/[team]/collections | CRUD collection management |
| Members | /admin/[team]/team/ | Team member management |
| Invitations | /admin/[team]/team/invitations | Pending invitations |
| Settings | /admin/[team]/team/settings | Team settings |
| Domains | /admin/[team]/team/domains | Custom domain management |
| Look & Feel | /admin/[team]/team/look-and-feel | Branding and appearance |
| App Admin Routes | /admin/[team]/{app} | Auto-discovered from apps |
Protected by team-admin middleware.
/super-admin/*)Routes for the app owner (system-wide management):
| Page | Path | Purpose |
|---|---|---|
| Dashboard | /super-admin | Stats overview and quick actions |
| Users | /super-admin/users | User management |
| Teams | /super-admin/teams | Team oversight |
Protected by super-admin middleware.
Two middleware options for different access levels:
Requires user to be a team admin or owner.
<script setup lang="ts">
definePageMeta({
middleware: ['auth', 'team-admin']
})
</script>
Requires user to be the app owner (super admin). Redirects non-admins to the home page.
<script setup lang="ts">
definePageMeta({
middleware: 'super-admin'
})
</script>
<template>
<div>Custom super admin page content</div>
</template>
Apps can register their routes to appear automatically in sidebars using croutonApps:
export default defineAppConfig({
croutonApps: {
bookings: {
id: 'bookings',
name: 'Bookings',
icon: 'i-lucide-calendar',
// User-facing routes (appear in /dashboard/[team]/ sidebar)
dashboardRoutes: [
{
path: 'bookings',
label: 'bookings.myBookings.title', // Translation key
icon: 'i-lucide-calendar'
}
],
// Admin routes (appear in /admin/[team]/ sidebar)
adminRoutes: [
{
path: 'bookings',
label: 'bookings.admin.title',
icon: 'i-lucide-calendar'
}
],
// Settings pages (appear in /admin/[team]/settings/)
settingsRoutes: [
{
path: 'email-templates',
label: 'bookings.settings.emailTemplates',
icon: 'i-lucide-mail'
}
]
}
}
})
const {
apps, // All registered apps
dashboardRoutes, // All user-facing routes
adminRoutes, // All admin routes
settingsRoutes, // All settings routes
getApp, // Get app by ID
hasApp // Check if app exists
} = useCroutonApps()
// Build navigation
const navItems = dashboardRoutes.value.map(route => ({
label: t(route.label),
icon: route.icon,
to: `/dashboard/${teamSlug}/${route.path}`
}))
interface CroutonAppRoute {
path: string // Route path segment
label: string // Translation key
icon?: string // Heroicon name
hidden?: boolean // Hide from sidebar
children?: CroutonAppRoute[]
}
interface CroutonAppConfig {
id: string
name: string
icon?: string
dashboardRoutes?: CroutonAppRoute[]
adminRoutes?: CroutonAppRoute[]
settingsRoutes?: CroutonAppRoute[]
}
User management operations.
const {
users, // List of users
total, // Total count
page, // Current page
loading, // Loading state
error, // Error message
getUsers, // Fetch users with filters
getUser, // Get user detail
createUser, // Create new user
banUser, // Ban a user
unbanUser, // Unban a user
deleteUser // Delete user permanently
} = useAdminUsers()
// Fetch users with filters
await getUsers({
page: 1,
pageSize: 20,
search: 'john',
status: 'active', // 'all' | 'active' | 'banned'
superAdmin: false,
sortBy: 'createdAt',
sortOrder: 'desc'
})
// Ban a user
await banUser('user-id', {
reason: 'Spam activity',
duration: 168 // hours (null = permanent)
})
Team management operations.
const {
teams, // List of teams
total, // Total count
loading, // Loading state
error, // Error message
getTeams, // Fetch teams with filters
getTeam // Get team detail with members
} = useAdminTeams()
// Fetch teams
await getTeams({
page: 1,
pageSize: 20,
search: 'acme',
personal: false,
sortBy: 'memberCount',
sortOrder: 'desc'
})
Dashboard statistics with optional auto-refresh.
const {
stats, // AdminStats object
loading, // Loading state
error, // Error message
refresh, // Fetch/refresh stats
startAutoRefresh,// Start auto-refresh
stopAutoRefresh // Stop auto-refresh
} = useAdminStats({ autoRefresh: true, refreshInterval: 30000 })
// Stats include:
// - totalUsers, newUsersToday, newUsersWeek
// - bannedUsers, superAdminCount
// - totalTeams, newTeamsWeek
// - activeSessions
User impersonation for debugging.
const {
isImpersonating, // Boolean: currently impersonating
impersonatedUser, // User being impersonated
originalAdminId, // Original admin's ID
loading, // Loading state
startImpersonation, // Start impersonating a user
stopImpersonation, // Return to admin account
checkStatus // Check current status
} = useImpersonation()
// Start impersonating
await startImpersonation('user-id')
// Stop impersonating
await stopImpersonation()
| Component | Purpose |
|---|---|
AdminDashboard | Full dashboard with stats and quick actions |
AdminStatsCard | Individual stat card with icon and trend |
<template>
<AdminDashboard
:stats="preloadedStats"
:show-quick-actions="true"
@navigate="handleNavigation"
/>
</template>
| Component | Purpose |
|---|---|
AdminUserList | Paginated user table with search and filters |
AdminUserActions | Dropdown menu for user actions |
AdminUserBanForm | Form for banning with reason and duration |
AdminUserCreateForm | Form for creating new users |
<template>
<AdminUserList
:page-size="20"
@user-selected="handleSelect"
/>
</template>
| Component | Purpose |
|---|---|
ImpersonationBanner | Top banner when impersonating a user |
Add to your app layout to show impersonation status:
<template>
<div>
<ImpersonationBanner />
<slot />
</div>
</template>
| Method | Endpoint | Purpose |
|---|---|---|
| GET | /api/admin/users | List users with pagination |
| GET | /api/admin/users/[id] | Get user detail |
| POST | /api/admin/users/create | Create new user |
| POST | /api/admin/users/ban | Ban a user |
| POST | /api/admin/users/unban | Unban a user |
| DELETE | /api/admin/users/delete | Delete a user |
| Method | Endpoint | Purpose |
|---|---|---|
| GET | /api/admin/teams | List teams |
| GET | /api/admin/teams/[id] | Get team with members |
| Method | Endpoint | Purpose |
|---|---|---|
| GET | /api/admin/stats | Get dashboard statistics |
| Method | Endpoint | Purpose |
|---|---|---|
| POST | /api/admin/impersonate/start | Start impersonation |
| POST | /api/admin/impersonate/stop | Stop impersonation |
| GET | /api/admin/impersonate/status | Check impersonation status |
Server-side authorization check. Use in custom admin endpoints.
export default defineEventHandler(async (event) => {
// Auto-imported server utility — no import needed
// Throws 403 if not super admin
const { user: adminUser } = await requireSuperAdmin(event)
// Your admin logic here
return { admin: adminUser.name }
})
Super admin privileges are stored in the superAdmin field on the user table.
import { db } from '@fyit/crouton-auth'
import { user } from '@fyit/crouton-auth/server/database/schema/auth'
import { eq } from 'drizzle-orm'
await db.update(user)
.set({ superAdmin: true })
.where(eq(user.email, 'admin@example.com'))
import { db } from '@fyit/crouton-auth'
import { user } from '@fyit/crouton-auth/server/database/schema/auth'
await db.insert(user).values({
id: 'admin-id',
email: 'admin@example.com',
name: 'Admin User',
emailVerified: true,
superAdmin: true
})
export default defineNuxtConfig({
runtimeConfig: {
public: {
crouton: {
admin: {
// Super admin route prefix (default: /super-admin)
routePrefix: '/super-admin',
// Enable impersonation feature
impersonation: true,
// Stats auto-refresh interval (ms)
statsRefreshInterval: 30000
}
}
}
}
})
Pre-defined ban duration options:
| Duration | Hours |
|---|---|
| 1 hour | 1 |
| 24 hours | 24 |
| 7 days | 168 |
| 30 days | 720 |
| 90 days | 2160 |
| Permanent | null |
Create your own page at the same path to override:
<script setup lang="ts">
definePageMeta({ middleware: 'super-admin' })
</script>
<template>
<div>
<h1>My Custom Super Admin Dashboard</h1>
<!-- Your custom content -->
</div>
</template>
Create your own ImpersonationBanner.vue:
<script setup lang="ts">
const { isImpersonating, impersonatedUser, stopImpersonation } = useImpersonation()
</script>
<template>
<div v-if="isImpersonating" class="my-custom-banner">
Impersonating: {{ impersonatedUser?.name }}
<button @click="stopImpersonation">Stop</button>
</div>
</template>
interface AdminStats {
totalUsers: number
newUsersToday: number
newUsersWeek: number
bannedUsers: number
totalTeams: number
newTeamsWeek: number
activeSessions: number
superAdminCount: number
}
interface BanPayload {
userId: string
reason: string
duration: number | null // hours, null = permanent
}
interface ImpersonationState {
isImpersonating: boolean
originalAdminId: string | null
impersonatedUser: {
id: string
name: string
email: string
} | null
}