npx skills add ...
npx skills add payloadcms/skills --skill payload
Use when working with Payload projects (payload.config.ts, collections, fields, hooks, access control, Payload API). Use when debugging validation errors, security issues, relationship queries, transactions, or hook behavior.
npx skills add payloadcms/skills --skill payload
Payload is a Next.js native CMS with TypeScript-first architecture, providing admin panel, database management, REST/GraphQL APIs, authentication, and file storage.
| Task | Solution | Details |
|---|---|---|
| Auto-generate slugs | slugField() | FIELDS.md#slug-field-helper |
| Restrict content by user | Access control with query | ACCESS-CONTROL.md#row-level-security-with-complex-queries |
| Local API user ops | user + overrideAccess: false | QUERIES.md#access-control-in-local-api |
| Draft/publish workflow | versions: { drafts: true } | COLLECTIONS.md#versioning--drafts |
| Computed fields | virtual: true with field-level hooks.afterRead returning the value | FIELDS.md#virtual-fields |
| Conditional fields | admin.condition | FIELDS.md#conditional-fields |
| Custom field validation | validate function | FIELDS.md#validation |
| Filter relationship list | filterOptions on field | FIELDS.md#relationship |
| Select specific fields | select parameter | QUERIES.md#field-selection |
| Auto-set author/dates | beforeChange hook | HOOKS.md#collection-hooks |
| Prevent hook loops | req.context check | HOOKS.md#context |
| Cascading deletes | beforeDelete hook | HOOKS.md#collection-hooks |
| Geospatial queries | point field with near/within | FIELDS.md#point-geolocation |
| Reverse relationships | join field type | FIELDS.md#join-fields |
| Next.js revalidation | Context control in afterChange | HOOKS.md#nextjs-revalidation-with-context-control |
| Query by relationship | Nested property syntax | QUERIES.md#nested-properties |
| Complex queries | AND/OR logic | QUERIES.md#andor-logic |
| Transactions | Pass req to operations | ADAPTERS.md#threading-req-through-operations |
| Background jobs | Jobs queue with tasks | ADVANCED.md#jobs-queue |
| Custom API routes | Collection custom endpoints | ADVANCED.md#custom-endpoints |
| Cloud storage | Storage adapter plugins | ADAPTERS.md#storage-adapters |
| Multi-language | localization config + localized: true | ADVANCED.md#localization |
| Create plugin | (options) => (config) => Config | PLUGIN-DEVELOPMENT.md#plugin-architecture |
| Plugin package setup | Package structure with SWC | PLUGIN-DEVELOPMENT.md#plugin-package-structure |
| Add fields to collection | Map collections, spread fields | PLUGIN-DEVELOPMENT.md#adding-fields-to-collections |
| Plugin hooks | Preserve existing hooks in array | PLUGIN-DEVELOPMENT.md#adding-hooks |
| Check field type | Type guard functions | FIELD-TYPE-GUARDS.md |
Apply these defaults when modeling content unless there's a clear reason not to:
versions: { drafts: true }. This is the
recommended starting point for any content collection. It auto-injects a
_status field (draft / published / changed) — don't add your own
status field, it's redundant. Only skip versions for collections that have
no publish/draft lifecycle (e.g. internal join tables, settings).slugField() for all slugs instead of hand-rolling
{ name: 'slug', type: 'text', unique: true }. It auto-generates the slug from
the title, adds a regenerate toggle, and handles uniqueness/indexing for you.
It defaults to generating from a title field — if the collection has no
title, pass the source field: slugField({ useAsSlug: 'name' }).position: 'sidebar' is for short, at-a-glance fields — status, category,
author, publish date. Avoid it for long fields that need horizontal space to be
usable (description, rich text content, long text). Those belong in the main
document area.For more collection patterns (auth, upload, drafts, live preview), see COLLECTIONS.md.
For all field types (array, blocks, point, join, virtual, conditional, etc.), see FIELDS.md.
Hooks live at one of two levels and they are not interchangeable. Collection hooks receive { doc, data, req, operation, ... } and act on the whole document. Field hooks live inside an individual field's hooks object, receive { value, siblingData, ... }, and return the new value for that field. Computed/virtual fields, per-field formatters, and per-field access masking are field hooks; cross-field business logic is a collection hook.
When asked to "compute a field" or "populate a field's value in a hook", use a field-level hook on that field — never a collection-level afterRead that mutates doc.
For all hook patterns, see HOOKS.md. For access control, see ACCESS-CONTROL.md.
For all query operators and REST/GraphQL examples, see QUERIES.md.
By default, Local API operations bypass ALL access control, even when passing a user.
When to use each:
overrideAccess: true (default) - Server-side operations you trust (cron jobs, system tasks)overrideAccess: false - When operating on behalf of a user (API routes, webhooks)See QUERIES.md#access-control-in-local-api.
Nested operations in hooks without req break transaction atomicity.
See ADAPTERS.md#threading-req-through-operations.
Hooks triggering operations that trigger the same hooks create infinite loops.
See HOOKS.md#context.
Payload generates payload-types.ts for you — you rarely need to run generate:types by hand.
typescript.autoGenerate defaults to true, so the dev
server regenerates types automatically whenever your config changes. Don't run
generate:types manually while the dev server is running — it's redundant.payload build generates the import map and types before
running next build. Prefer it over calling next build directly so neither is
ever stale. Pass --no-types to skip type generation.payload generate:types) is an escape hatch — only when
neither the dev server nor a build is in the loop (e.g. a one-off script, or CI
before a step that doesn't run payload build).overrideAccess: falsereq in nested operations breaks transaction atomicityreq.context flagsdepth: 0 for IDs only_status field is auto-injected when drafts are enabledautoGenerate) and during payload build — avoid running generate:types manuallytransactionOptions: {}versions: { drafts: true } by default on content collections; rely on the
auto-injected _status field rather than adding a custom status fieldslugField() for slugs instead of hand-rolling a unique text fieldposition: 'sidebar' for short, at-a-glance fields (status, category,
author, date); keep long fields (description, rich text) in the main areaoverrideAccess: false when passing user to Local APIsaveToJWT: true for roles to avoid database lookupsselect to limit returned fieldsmaxDepth on relationships to prevent over-fetchingreq.contextreq to nested operations in hooksbeforeValidate for data formattingbeforeChange for business logicautoGenerate) and payload build generate types; run generate:types manually only when neither is runningpayload-types.tsimport type { User } from '@/payload-types'CollectionConfig, Field, CollectionBeforeChangeHook, Access, Plugin, …) or use satisfies <Type>. Without an annotation, string properties like type: 'text' widen to string and discriminated unions (Field, CollectionConfig) fail to resolve. Inline literals get this for free via contextual typing; extracted constants do not.access/ directoryhooks/ directory