Custom Pages
Custom Pages
Add custom pages to your admin panel for features beyond standard CRUD operations.
Overview
Custom pages allow you to add specialized admin functionality like:
- Analytics dashboards
- Report generators
- Settings pages
- Custom workflows
- Data import/export interfaces
Configuration
Add custom pages in your nuxt.config.ts:
export default defineNuxtConfig({
autoAdmin: {
customPages: [
{
name: 'analytics',
label: 'Analytics',
path: 'analytics',
icon: 'i-heroicons-chart-bar',
group: 'Reports',
order: 1,
},
{
name: 'settings',
label: 'Settings',
path: 'settings',
icon: 'i-heroicons-cog-6-tooth',
order: 999,
},
],
},
})
Page Options
name
Unique identifier for the page:
{
name: 'analytics', // Must be unique
}
label
Display text shown in the sidebar:
{
label: 'Analytics Dashboard',
}
path
URL path relative to admin prefix:
{
path: 'analytics', // Accessible at /admin/analytics
}
For absolute paths, use a leading slash:
{
path: '/custom-admin/analytics', // Absolute path
}
icon
Heroicon name for sidebar:
{
icon: 'i-heroicons-chart-bar',
}
Browse icons at: https://heroicons.com/
group
Organize custom pages into sidebar groups:
{
group: 'Reports', // Grouped with other report pages
}
Pages without a group appear in the ungrouped section.
order
Control display order within groups:
{
order: 1, // Lower numbers appear first
}
Creating Page Components
Create a Vue component in your Nuxt pages directory:
# For path: 'analytics'
touch app/pages/admin/analytics.vue
<!-- app/pages/admin/analytics.vue -->
<template>
<div class="space-y-4">
<div>
<h1 class="text-2xl font-semibold text-gray-900 dark:text-white">
Analytics Dashboard
</h1>
<p class="text-sm text-gray-500 dark:text-gray-400 mt-1">
View your site statistics
</p>
</div>
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
<UCard>
<div class="text-center">
<div class="text-3xl font-bold">{{ stats.users }}</div>
<div class="text-sm text-gray-500">Total Users</div>
</div>
</UCard>
<UCard>
<div class="text-center">
<div class="text-3xl font-bold">{{ stats.posts }}</div>
<div class="text-sm text-gray-500">Total Posts</div>
</div>
</UCard>
<UCard>
<div class="text-center">
<div class="text-3xl font-bold">{{ stats.comments }}</div>
<div class="text-sm text-gray-500">Total Comments</div>
</div>
</UCard>
</div>
</div>
</template>
<script setup lang="ts">
definePageMeta({
layout: 'admin', // Use admin layout
})
const stats = ref({
users: 0,
posts: 0,
comments: 0,
})
onMounted(async () => {
// Fetch stats from API
const [users, posts, comments] = await Promise.all([
$fetch('/api/users', { query: { aggregate: 'count' } }),
$fetch('/api/posts', { query: { aggregate: 'count' } }),
$fetch('/api/comments', { query: { aggregate: 'count' } }),
])
stats.value = {
users: users.meta.aggregates.count,
posts: posts.meta.aggregates.count,
comments: comments.meta.aggregates.count,
}
})
</script>
Important: Always include definePageMeta({ layout: 'admin' }) to use the admin layout.
Permission Control
Using canAccess Function
For complex permission logic:
{
name: 'analytics',
label: 'Analytics',
path: 'analytics',
icon: 'i-heroicons-chart-bar',
canAccess: async (user) => {
// Check if user has permission
return user?.role === 'admin' || user?.permissions?.includes('analytics.view')
},
}
The function receives the current user and should return a boolean (or Promise
Using Permission Strings
For simple permission checks:
{
name: 'settings',
label: 'Settings',
path: 'settings',
icon: 'i-heroicons-cog-6-tooth',
permissions: 'admin', // Single permission
}
Or require multiple permissions:
{
permissions: ['admin', 'settings.manage'], // User needs ALL
}
Note: Permission string validation is a planned feature. Currently, use canAccess for actual permission checking.
Sidebar Behavior
Custom pages respect the unauthorizedSidebarItems setting:
autoAdmin: {
permissions: {
unauthorizedSidebarItems: 'hide', // or 'disable'
},
}
- 'hide' - Pages without access are removed from sidebar
- 'disable' - Pages without access appear grayed out
Navigation Protection
The global middleware automatically protects custom pages:
- Checks
canAccessfunction before allowing access - Returns 403 Forbidden if unauthorized
- Shows nice error page with navigation options
Complete Examples
Analytics Dashboard
// nuxt.config.ts
customPages: [
{
name: 'analytics',
label: 'Analytics',
path: 'analytics',
icon: 'i-heroicons-chart-bar',
group: 'Reports',
canAccess: async (user) => {
return user?.permissions?.includes('analytics.view')
},
},
]
Settings Page
// nuxt.config.ts
customPages: [
{
name: 'settings',
label: 'Settings',
path: 'settings',
icon: 'i-heroicons-cog-6-tooth',
order: 999, // Show at bottom
canAccess: async (user) => {
return user?.role === 'admin'
},
},
]
<!-- app/pages/admin/settings.vue -->
<template>
<div class="space-y-4">
<h1 class="text-2xl font-semibold">Settings</h1>
<UCard>
<UForm :state="settings" @submit="saveSettings">
<div class="space-y-4">
<UFormGroup label="Site Name">
<UInput v-model="settings.siteName" />
</UFormGroup>
<UFormGroup label="Contact Email">
<UInput v-model="settings.contactEmail" type="email" />
</UFormGroup>
<UFormGroup label="Maintenance Mode">
<UToggle v-model="settings.maintenanceMode" />
</UFormGroup>
<UButton type="submit">Save Settings</UButton>
</div>
</UForm>
</UCard>
</div>
</template>
<script setup lang="ts">
definePageMeta({
layout: 'admin',
})
const settings = ref({
siteName: '',
contactEmail: '',
maintenanceMode: false,
})
onMounted(async () => {
const data = await $fetch('/api/settings')
settings.value = data
})
async function saveSettings() {
await $fetch('/api/settings', {
method: 'PUT',
body: settings.value,
})
// Show success notification
}
</script>
Data Export Page
// nuxt.config.ts
customPages: [
{
name: 'export',
label: 'Export Data',
path: 'export',
icon: 'i-heroicons-arrow-down-tray',
group: 'Tools',
canAccess: async (user) => {
return user?.permissions?.includes('data.export')
},
},
]
<!-- app/pages/admin/export.vue -->
<template>
<div class="space-y-4">
<h1 class="text-2xl font-semibold">Export Data</h1>
<UCard>
<div class="space-y-4">
<UFormGroup label="Select Resource">
<USelect
v-model="selectedResource"
:options="resources"
/>
</UFormGroup>
<UFormGroup label="Format">
<USelect
v-model="format"
:options="['CSV', 'JSON', 'Excel']"
/>
</UFormGroup>
<UButton
:loading="isExporting"
@click="exportData"
>
Export
</UButton>
</div>
</UCard>
</div>
</template>
<script setup lang="ts">
definePageMeta({
layout: 'admin',
})
const selectedResource = ref('users')
const format = ref('CSV')
const isExporting = ref(false)
const resources = ['users', 'posts', 'comments']
async function exportData() {
isExporting.value = true
try {
const blob = await $fetch(`/api/export/${selectedResource.value}`, {
query: { format: format.value },
})
// Download file
const url = URL.createObjectURL(blob)
const a = document.createElement('a')
a.href = url
a.download = `${selectedResource.value}.${format.value.toLowerCase()}`
a.click()
} finally {
isExporting.value = false
}
}
</script>
Best Practices
- Always use admin layout - Include
definePageMeta({ layout: 'admin' }) - Implement permission checks - Use
canAccessfor security - Follow naming conventions - Use kebab-case for paths
- Group related pages - Use groups for better organization
- Provide clear labels - Make purpose obvious in sidebar
- Handle loading states - Show loading indicators
- Error handling - Handle API errors gracefully
Accessing Admin Data
Custom pages can access all admin composables:
<script setup>
// Get current user permissions
const { canCreate, canUpdate } = useAdminPermissions('posts')
// Access admin configuration
const { branding } = useAdminConfig()
// Get resource schemas
const { resource } = useAdminResource('users')
// Use admin actions
const { goToList, goToCreate } = useAdminActions('posts')
</script>
Next Steps
- M2M Relationships - Manage complex relations
- Custom Actions - Add custom resource actions
- Composables - Available composables reference
Permissions
This document explains the comprehensive permission system implemented in Nuxt Auto Admin.
M2M Relationships
This guide covers how M2M relationships work in the auto-admin interface.