6.2 KiB
6.2 KiB
| tags | topic | sources | created | ||||||
|---|---|---|---|---|---|---|---|---|---|
|
payloadcms |
|
2026-05-15 |
PayloadCMS — Admin Panel
Overview
- Payload auto-generates a full CRUD admin panel built on Next.js App Router with RSC support
- Panel lives at
app/(payload)/admin/[[...segments]]/page.tsx— all routes are Next.js routes - REST (
/api) and GraphQL (/graphql) APIs live side-by-side in the same route group - Fully white-label capable: custom CSS, custom React components, custom routes
- 30+ language i18n, light/dark mode, per-user preferences persisted across sessions
Config / Setup
import { buildConfig } from 'payload'
const config = buildConfig({
admin: {
user: 'admins', // auth-enabled collection slug
dateFormat: 'dd/MM/yyyy',
theme: 'dark', // 'light' | 'dark' | 'all'
meta: { titleSuffix: '— MyApp' },
livePreview: { url: '...' },
timezones: {
defaultTimezone: 'Europe/Amsterdam',
supportedTimezones: ({ defaultTimezones }) => [
...defaultTimezones,
{ label: 'UTC', value: 'UTC' },
],
},
toast: { duration: 6000, position: 'bottom-right', limit: 3 },
routes: {
account: '/my-account',
login: '/login',
},
},
routes: {
admin: '/dashboard', // move panel off /admin
api: '/api',
},
})
Key Options
| Option | Default | Notes |
|---|---|---|
admin.user |
'users' |
Slug of auth-enabled collection |
admin.theme |
'all' |
Restrict to light / dark |
admin.dateFormat |
ISO | Any date-fns pattern |
admin.timezones |
— | DB always stores UTC; display only |
admin.toast.duration |
4000 ms |
Uses Sonner library |
routes.admin |
/admin |
Moving requires updating Next.js dir structure |
Admin-level routes (behind /admin)
account, createFirstUser, forgot, inactivity, login, logout, reset, unauthorized
Preview
Preview generates a link from the admin edit view to your front-end. Different from Live Preview (iframe).
// Collection config
admin: {
preview: (doc, { req, token }) =>
doc?.enabled ? `${req.protocol}//${req.host}/${doc.slug}` : null,
}
Draft Preview (Next.js)
- Preview URL →
/preview?slug=...&previewSecret=...&path=... - Route handler at
/app/preview/route.tsverifies secret, callspayload.auth(), callsdraftMode().enable(), redirects topath - Page component reads
(await draftMode()).isEnabledand passesdraft: truetopayload.find()
Preferences API
Per-user persistent preferences stored in payload-preferences collection.
'use client'
import { usePreferences } from '@payloadcms/ui'
export function ColorPicker() {
const { getPreference, setPreference } = usePreferences()
useEffect(() => {
getPreference<string[]>('last-used-colors').then(setColors)
}, [getPreference])
const addColor = (color: string) =>
setPreference('last-used-colors', [...colors, color])
}
getPreference(key) → Promise<T> | setPreference(key, value) → void
React Hooks (client components only)
All hooks from @payloadcms/ui. Require 'use client' directive.
Form hooks
| Hook | Purpose |
|---|---|
useField({ path }) |
Read/write a single field's value + state |
useFormFields(selector) |
Select specific fields — minimal re-renders |
useAllFormFields() |
Full form state + dispatchFields |
useForm() |
Form methods: submit, validateForm, getData, reset, addFieldRow etc. |
useDocumentForm() |
Top-level form — useful inside Lexical block child forms |
'use client'
import type { TextFieldClientComponent } from 'payload'
import { useField } from '@payloadcms/ui'
export const CustomTextField: TextFieldClientComponent = ({ path }) => {
const { value, setValue } = useField<string>({ path })
return <input value={value ?? ''} onChange={e => setValue(e.target.value)} />
}
Lexical gotcha: setValue updates form data but does NOT re-render the editor UI. Use dispatchFields with both value and initialValue:
dispatchFields({ type: 'UPDATE', path: 'body', value: newVal, initialValue: newVal })
Document / collection hooks
| Hook | Returns |
|---|---|
useDocumentInfo() |
id, collectionSlug, globalSlug, docPermissions, isLocked, data, uploadStatus, … |
useDocumentTitle() |
title, setDocumentTitle |
useListQuery() |
data, query, handlePageChange, handleWhereChange, … |
useSelection() |
count, selected, toggleAll, totalDocs |
Auth / config hooks
| Hook | Returns |
|---|---|
useAuth<User>() |
user, logOut, token, refreshPermissions, permissions |
useConfig() |
config, getEntityConfig({ collectionSlug }) |
useLocale() |
{ code, label, rtl } |
useTheme() |
theme, setTheme, autoMode |
usePreferences() |
getPreference, setPreference |
Misc hooks
useCollapsible()—isCollapsed,toggleuseEditDepth()— nesting depth in modalsuseStepNav()— breadcrumb control:setStepNav(items)useDocumentEvents()— cross-document update eventsusePayloadAPI(url, options)— reactive REST fetching:[{ data, isLoading, isError }, { setParams }]useTableColumns()—setActiveColumns,toggleColumn,resetColumnsStateuseRouteTransition()—startRouteTransitionfor programmatic navigation
Gotchas
importMap.jsis auto-regenerated on startup/HMR — never edit it manually;layout.tsxis safe to edit- Moving the admin panel (
routes.admin) requires restructuring the Next.jsapp/folder to match - Timezones are display-only; DB always stores UTC — must also enable
timezone: trueon each date field - Multiple auth collections possible but only one can access the admin panel (
admin.user) - Client Components cannot receive non-serializable props from Server Components