admin-crud
Generate admin dashboard pages with data tables, filters, bulk actions, dialogs, and forms. Use when building admin interfaces, management pages, or dashboard components.
Best use case
admin-crud is best used when you need a repeatable AI agent workflow instead of a one-off prompt.
Generate admin dashboard pages with data tables, filters, bulk actions, dialogs, and forms. Use when building admin interfaces, management pages, or dashboard components.
Teams using admin-crud should expect a more consistent output, faster repeated execution, less prompt rewriting.
When to use this skill
- You want a reusable workflow that can be run more than once with consistent structure.
When not to use this skill
- You only need a quick one-off answer and do not need a reusable workflow.
- You cannot install or maintain the underlying files, dependencies, or repository context.
Installation
Claude Code / Cursor / Codex
Manual Installation
- Download SKILL.md from GitHub
- Place it in
.claude/skills/admin-crud/SKILL.mdinside your project - Restart your AI agent — it will auto-discover the skill
How admin-crud Compares
| Feature / Agent | admin-crud | Standard Approach |
|---|---|---|
| Platform Support | Not specified | Limited / Varies |
| Context Awareness | High | Baseline |
| Installation Complexity | Unknown | N/A |
Frequently Asked Questions
What does this skill do?
Generate admin dashboard pages with data tables, filters, bulk actions, dialogs, and forms. Use when building admin interfaces, management pages, or dashboard components.
Where can I find the source code?
You can find the source code on GitHub using the link provided at the top of the page.
SKILL.md Source
# Admin CRUD Generator
Create admin dashboard pages following this project's established patterns.
## Admin Page Structure
```
src/
├── routes/admin/
│ └── resources/
│ ├── index.tsx # List page
│ └── $resourceId.tsx # Detail/edit page
└── components/admin/
└── resources/
├── ResourcesList.tsx # List container
├── ResourceForm.tsx # Create/edit form
└── components/
├── ResourceTable.tsx # Data table
├── StatusBadge.tsx # Status indicator
├── BulkActionsBar.tsx # Bulk operations
└── ResourceActions.tsx # Row actions
```
## List Page Template
```typescript
// src/routes/admin/resources/index.tsx
import { createFileRoute } from '@tanstack/react-router'
import { ResourcesList } from '@/components/admin/resources/ResourcesList'
export const Route = createFileRoute('/admin/resources/')({
component: ResourcesPage,
})
function ResourcesPage() {
return <ResourcesList />
}
```
## List Component with Data Table
```typescript
// src/components/admin/resources/ResourcesList.tsx
import { useQuery } from '@tanstack/react-query'
import { useTranslation } from 'react-i18next'
import { Plus } from 'lucide-react'
import { Link } from '@tanstack/react-router'
import { Button } from '@/components/ui/button'
import { ResourceTable } from './components/ResourceTable'
export function ResourcesList() {
const { t } = useTranslation()
const { data, isLoading, error } = useQuery({
queryKey: ['resources'],
queryFn: async () => {
const res = await fetch('/api/resources', { credentials: 'include' })
const json = await res.json()
if (!json.success) throw new Error(json.error)
return json
},
})
if (isLoading) {
return (
<div className="flex items-center justify-center h-64">
<div
className="animate-spin rounded-full h-8 w-8 border-t-2 border-b-2 border-pink-500"
role="status"
aria-label="Loading"
/>
</div>
)
}
if (error) {
return (
<div className="text-center py-12">
<p className="text-red-500">{t('Failed to load')}</p>
</div>
)
}
const { items, total } = data
if (items.length === 0) {
return (
<div className="text-center py-12">
<Package className="mx-auto h-12 w-12 text-muted-foreground" />
<h3 className="mt-2 text-sm font-semibold">{t('No resources')}</h3>
<p className="mt-1 text-sm text-muted-foreground">
{t('Get started by creating a new resource.')}
</p>
<div className="mt-6">
<Button asChild>
<Link to="/admin/resources/new">
<Plus className="mr-2 h-4 w-4" />
{t('Add Resource')}
</Link>
</Button>
</div>
</div>
)
}
return (
<div className="space-y-4">
<div className="flex items-center justify-between">
<h1 className="text-2xl font-bold">{t('Resources')}</h1>
<Button asChild>
<Link to="/admin/resources/new">
<Plus className="mr-2 h-4 w-4" />
{t('Add Resource')}
</Link>
</Button>
</div>
<ResourceTable resources={items} />
<div className="text-sm text-muted-foreground">
{t('{{count}} total', { count: total })}
</div>
</div>
)
}
```
## Data Table Component
```typescript
// src/components/admin/resources/components/ResourceTable.tsx
import { useState } from 'react'
import { Link } from '@tanstack/react-router'
import { MoreHorizontal, ArrowUpDown, ArrowUp, ArrowDown } from 'lucide-react'
import { useTranslation } from 'react-i18next'
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu'
import { Button } from '@/components/ui/button'
import { Checkbox } from '@/components/ui/checkbox'
import { StatusBadge } from './StatusBadge'
interface Resource {
id: string
name: { en: string }
status: 'active' | 'draft' | 'archived'
createdAt: string
}
interface Props {
resources: Resource[]
}
export function ResourceTable({ resources }: Props) {
const { t } = useTranslation()
const [selectedIds, setSelectedIds] = useState<Set<string>>(new Set())
const [sortKey, setSortKey] = useState<string>('createdAt')
const [sortOrder, setSortOrder] = useState<'asc' | 'desc'>('desc')
const toggleSelect = (id: string) => {
const next = new Set(selectedIds)
if (next.has(id)) {
next.delete(id)
} else {
next.add(id)
}
setSelectedIds(next)
}
const toggleSelectAll = () => {
if (selectedIds.size === resources.length) {
setSelectedIds(new Set())
} else {
setSelectedIds(new Set(resources.map((r) => r.id)))
}
}
const isAllSelected = selectedIds.size === resources.length
const isSomeSelected = selectedIds.size > 0 && selectedIds.size < resources.length
const handleSort = (key: string) => {
if (sortKey === key) {
setSortOrder(sortOrder === 'asc' ? 'desc' : 'asc')
} else {
setSortKey(key)
setSortOrder('desc')
}
}
const SortIcon = ({ columnKey }: { columnKey: string }) => {
if (sortKey !== columnKey) return <ArrowUpDown className="ml-2 h-4 w-4" />
return sortOrder === 'asc'
? <ArrowUp className="ml-2 h-4 w-4" />
: <ArrowDown className="ml-2 h-4 w-4" />
}
return (
<>
{selectedIds.size > 0 && (
<BulkActionsBar
selectedCount={selectedIds.size}
onClearSelection={() => setSelectedIds(new Set())}
/>
)}
<div className="rounded-md border">
<table className="w-full">
<thead>
<tr className="border-b bg-muted/50">
<th className="w-12 p-4">
<Checkbox
checked={isAllSelected}
indeterminate={isSomeSelected}
onCheckedChange={toggleSelectAll}
/>
</th>
<th className="p-4 text-left">
<button
className="flex items-center font-medium"
onClick={() => handleSort('name')}
>
{t('Name')}
<SortIcon columnKey="name" />
</button>
</th>
<th className="p-4 text-left">{t('Status')}</th>
<th className="p-4 text-left">
<button
className="flex items-center font-medium"
onClick={() => handleSort('createdAt')}
>
{t('Created')}
<SortIcon columnKey="createdAt" />
</button>
</th>
<th className="w-12 p-4"></th>
</tr>
</thead>
<tbody>
{resources.map((resource) => (
<tr
key={resource.id}
className={`border-b hover:bg-muted/50 group ${
selectedIds.has(resource.id) ? 'bg-pink-500/5' : ''
}`}
>
<td className="p-4">
<Checkbox
checked={selectedIds.has(resource.id)}
onCheckedChange={() => toggleSelect(resource.id)}
/>
</td>
<td className="p-4">
<Link
to="/admin/resources/$resourceId"
params={{ resourceId: resource.id }}
className="font-medium hover:underline"
>
{resource.name.en}
</Link>
</td>
<td className="p-4">
<StatusBadge status={resource.status} />
</td>
<td className="p-4 text-muted-foreground">
{new Date(resource.createdAt).toLocaleDateString()}
</td>
<td className="p-4">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" size="icon">
<MoreHorizontal className="h-4 w-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem asChild>
<Link
to="/admin/resources/$resourceId"
params={{ resourceId: resource.id }}
>
{t('Edit')}
</Link>
</DropdownMenuItem>
<DropdownMenuItem className="text-destructive">
{t('Delete')}
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</td>
</tr>
))}
</tbody>
</table>
</div>
</>
)
}
```
## Status Badge
```typescript
// src/components/admin/resources/components/StatusBadge.tsx
import { cn } from '@/lib/utils'
const statusStyles = {
active: 'bg-emerald-500/10 text-emerald-500',
draft: 'bg-amber-500/10 text-amber-500',
archived: 'bg-muted text-muted-foreground',
pending: 'bg-blue-500/10 text-blue-500',
processing: 'bg-purple-500/10 text-purple-500',
shipped: 'bg-cyan-500/10 text-cyan-500',
delivered: 'bg-emerald-500/10 text-emerald-500',
cancelled: 'bg-red-500/10 text-red-500',
paid: 'bg-emerald-500/10 text-emerald-500',
failed: 'bg-red-500/10 text-red-500',
refunded: 'bg-amber-500/10 text-amber-500',
}
interface Props {
status: keyof typeof statusStyles
}
export function StatusBadge({ status }: Props) {
return (
<span
className={cn(
'inline-flex items-center gap-1.5 rounded-full px-2 py-1 text-xs font-medium uppercase',
statusStyles[status] || statusStyles.draft
)}
>
<span className="h-1.5 w-1.5 rounded-full bg-current" />
{status}
</span>
)
}
```
## Bulk Actions Bar
```typescript
// src/components/admin/resources/components/BulkActionsBar.tsx
import { useMutation, useQueryClient } from '@tanstack/react-query'
import { useTranslation } from 'react-i18next'
import { toast } from 'sonner'
import { Button } from '@/components/ui/button'
interface Props {
selectedCount: number
selectedIds: string[]
onClearSelection: () => void
}
export function BulkActionsBar({ selectedCount, selectedIds, onClearSelection }: Props) {
const { t } = useTranslation()
const queryClient = useQueryClient()
const bulkUpdate = useMutation({
mutationFn: async (action: 'activate' | 'archive' | 'delete') => {
const res = await fetch('/api/resources/bulk', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
credentials: 'include',
body: JSON.stringify({ ids: selectedIds, action }),
})
const json = await res.json()
if (!json.success) throw new Error(json.error)
return json
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['resources'] })
onClearSelection()
toast.success(t('Updated successfully'))
},
onError: (error) => {
toast.error(error.message)
},
})
return (
<div className="flex items-center gap-4 rounded-lg border bg-muted/50 p-4">
<span className="text-sm font-medium">
{t('{{count}} selected', { count: selectedCount })}
</span>
<div className="flex gap-2">
<Button
size="sm"
variant="outline"
onClick={() => bulkUpdate.mutate('activate')}
disabled={bulkUpdate.isPending}
>
{t('Activate')}
</Button>
<Button
size="sm"
variant="outline"
onClick={() => bulkUpdate.mutate('archive')}
disabled={bulkUpdate.isPending}
>
{t('Archive')}
</Button>
<Button
size="sm"
variant="destructive"
onClick={() => bulkUpdate.mutate('delete')}
disabled={bulkUpdate.isPending}
>
{t('Delete')}
</Button>
</div>
<Button
size="sm"
variant="ghost"
onClick={onClearSelection}
className="ml-auto"
>
{t('Clear selection')}
</Button>
</div>
)
}
```
## Card-Based Form Layout
```typescript
// Product form pattern with gradient accent cards
<div className="grid lg:grid-cols-3 gap-6">
{/* Main content - 2 columns */}
<div className="lg:col-span-2 space-y-6">
{/* Details Card */}
<Card className="border-border/50 shadow-xl shadow-foreground/5 bg-card/50 backdrop-blur-sm overflow-hidden">
<div className="h-1 bg-gradient-to-r from-pink-500 to-purple-500" />
<CardHeader>
<CardTitle>{t('Details')}</CardTitle>
</CardHeader>
<CardContent>
{/* Form fields */}
</CardContent>
</Card>
{/* Media Card */}
<Card className="overflow-hidden">
<div className="h-1 bg-gradient-to-r from-violet-500 to-fuchsia-500" />
<CardHeader>
<CardTitle>{t('Media')}</CardTitle>
</CardHeader>
<CardContent>
{/* Image uploader */}
</CardContent>
</Card>
</div>
{/* Sidebar - 1 column, sticky */}
<div className="space-y-6">
<div className="lg:sticky lg:top-4">
{/* Status Card */}
<Card>
<div className="h-1 bg-gradient-to-r from-emerald-500 to-teal-500" />
<CardHeader>
<CardTitle>{t('Status')}</CardTitle>
</CardHeader>
<CardContent>
{/* Status select */}
</CardContent>
</Card>
</div>
</div>
</div>
```
## Gradient Accent Colors
| Section | Gradient |
| -------- | -------------------------------- |
| Details | `from-pink-500 to-purple-500` |
| Media | `from-violet-500 to-fuchsia-500` |
| Options | `from-blue-500 to-cyan-500` |
| Variants | `from-emerald-500 to-teal-500` |
| SEO | `from-amber-500 to-orange-500` |
## Confirmation Dialog
```typescript
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
} from '@/components/ui/alert-dialog'
function DeleteConfirmDialog({ open, onOpenChange, onConfirm, resourceName }) {
const { t } = useTranslation()
return (
<AlertDialog open={open} onOpenChange={onOpenChange}>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>{t('Delete Resource')}</AlertDialogTitle>
<AlertDialogDescription>
{t('Are you sure you want to delete "{{name}}"? This action cannot be undone.', {
name: resourceName,
})}
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>{t('Cancel')}</AlertDialogCancel>
<AlertDialogAction
onClick={onConfirm}
className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
>
{t('Delete')}
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
)
}
```
## See Also
- `src/components/admin/products/` - Full product CRUD example
- `src/components/admin/orders/` - Order management
- `src/hooks/useDataTable.ts` - Table state management
- `forms` skill - Form patterns with FNFormRelated Skills
api-admin-ops
Autonomous API administration agent for monitoring, managing, and troubleshooting third-party API integrations. Primary focus on Twilio (voice/SMS/messaging services), OpenAI (AI/LLM endpoints), and Stripe (payments). Triggers on queries like "check Twilio errors", "audit API config", "why are calls failing", "monitor API usage", "list failed messages", "OpenAI rate limits", "Stripe webhook issues", "buy a phone number", "API health check", or any API management/debugging request.
anthropic_administrator-automation
Automate Anthropic Admin tasks via Rube MCP (Composio): API keys, usage, workspaces, and organization management. Always search tools first for current schemas.
admin-interface-rules
Rules for the Admin interface functionalities
admin-dashboard
Extend and modify the admin dashboard, developer portal, and operations console. Use when adding new admin tabs, metrics, monitoring features, or internal tools. Activates for dashboard development, analytics, user management, and internal tooling.
admin-design
Minimal, high-clarity admin UI design for this repo. Use when redesigning /admin pages (translation manager, dashboards, tables, forms), defining admin design tokens, or improving admin UX/keyboard workflows without changing core functionality.
admin-chat-ui-flow
Padroniza UX visual e fluxo da tela de chat admin com estilo da area cliente.
database-admin
Expert database administrator specializing in modern cloud databases, automation, and reliability engineering. Masters AWS/Azure/GCP database services, Infrastructure as Code, high availability, disaster recovery, performance optimization, and compliance. Handles multi-cloud strategies, container databases, and cost optimization. Use PROACTIVELY for database architecture, operations, or reliability engineering.
api-platform-crud
Create complete REST API CRUD operations using API Platform 4 with DDD and CQRS patterns. Use when adding new API resources, implementing CRUD endpoints, creating DTOs, configuring operations, or setting up state processors. Follows the repository's hexagonal architecture with YAML resource configuration and command bus pattern.
agent-database-administrator
Expert database administrator specializing in high-availability systems, performance optimization, and disaster recovery. Masters PostgreSQL, MySQL, MongoDB, and Redis with focus on reliability, scalability, and operational excellence.
Add Admin API Endpoint
Add a new endpoint or endpoints to Ghost's Admin API at `ghost/api/admin/**`.
admin-documents
Document management, LLM pipeline, anonymization, Q&A generation, versioning
bgo
Automates the complete Blender build-go workflow, from building and packaging your extension/add-on to removing old versions, installing, enabling, and launching Blender for quick testing and iteration.