177 lines
6.2 KiB
Markdown
177 lines
6.2 KiB
Markdown
---
|
|
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<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 |
|
|
|
|
```tsx
|
|
'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`:
|
|
|
|
```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>()` | `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]]
|