obsidian/wiki/payloadcms/admin.md
2026-05-15 15:13:56 +01:00

6.2 KiB

tags topic sources created
payloadcms
tech-patterns
payloadcms
raw/admin__overview.md
raw/admin__preferences.md
raw/admin__preview.md
raw/admin__react-hooks.md
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)

  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.

'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, 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