--- tags: [payloadcms, tech-patterns] topic: payloadcms sources: [raw/admin__overview.md, raw/admin__preferences.md, raw/admin__preview.md, raw/admin__react-hooks.md] created: 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 ```ts 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). ```ts // Collection config admin: { preview: (doc, { req, token }) => doc?.enabled ? `${req.protocol}//${req.host}/${doc.slug}` : null, } ``` ### Draft Preview (Next.js) 1. Preview URL → `/preview?slug=...&previewSecret=...&path=...` 2. Route handler at `/app/preview/route.ts` verifies secret, calls `payload.auth()`, calls `draftMode().enable()`, redirects to `path` 3. Page component reads `(await draftMode()).isEnabled` and passes `draft: true` to `payload.find()` ## Preferences API Per-user persistent preferences stored in `payload-preferences` collection. ```tsx 'use client' import { usePreferences } from '@payloadcms/ui' export function ColorPicker() { const { getPreference, setPreference } = usePreferences() useEffect(() => { getPreference('last-used-colors').then(setColors) }, [getPreference]) const addColor = (color: string) => setPreference('last-used-colors', [...colors, color]) } ``` `getPreference(key)` → `Promise` | `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 | ```tsx 'use client' import type { TextFieldClientComponent } from 'payload' import { useField } from '@payloadcms/ui' export const CustomTextField: TextFieldClientComponent = ({ path }) => { const { value, setValue } = useField({ path }) return 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`: ```tsx 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`, `logOut`, `token`, `refreshPermissions`, `permissions` | | `useConfig()` | `config`, `getEntityConfig({ collectionSlug })` | | `useLocale()` | `{ code, label, rtl }` | | `useTheme()` | `theme`, `setTheme`, `autoMode` | | `usePreferences()` | `getPreference`, `setPreference` | ### Misc hooks - `useCollapsible()` — `isCollapsed`, `toggle` - `useEditDepth()` — nesting depth in modals - `useStepNav()` — breadcrumb control: `setStepNav(items)` - `useDocumentEvents()` — cross-document update events - `usePayloadAPI(url, options)` — reactive REST fetching: `[{ data, isLoading, isError }, { setParams }]` - `useTableColumns()` — `setActiveColumns`, `toggleColumn`, `resetColumnsState` - `useRouteTransition()` — `startRouteTransition` for programmatic navigation ## Gotchas - `importMap.js` is auto-regenerated on startup/HMR — never edit it manually; `layout.tsx` is safe to edit - Moving the admin panel (`routes.admin`) requires restructuring the Next.js `app/` folder to match - Timezones are display-only; DB always stores UTC — must also enable `timezone: true` on 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 ## Related - [[wiki/payloadcms/custom-components|Custom Components]] - [[wiki/payloadcms/admin-panel-overview|Admin Panel Overview]] - [[wiki/payloadcms/configuration|Configuration]]