mirror of
https://github.com/payloadcms/skills.git
synced 2026-09-14 15:59:32 +08:00
feat(payload): sync skill with latest
Update SKILL.md and reference docs to match the upstream source in tools/claude-plugin. Add missing reference files to README structure.
This commit is contained in:
@@ -16,7 +16,7 @@ The `payload` skill provides expert guidance on:
|
||||
|
||||
## Usage
|
||||
|
||||
Once installed, the Agent will automatically invoke the skill when you're working on Payload CMS projects. The skill activates when you:
|
||||
Once installed, the Agent will automatically invoke the skill when you're working on Payload projects. The skill activates when you:
|
||||
|
||||
- Edit `payload.config.ts` files
|
||||
- Work with collection or global configurations
|
||||
@@ -36,12 +36,15 @@ skills/payload/
|
||||
├── SKILL.md # Main skill file with quick reference
|
||||
└── reference/
|
||||
├── FIELDS.md # All field types and configurations
|
||||
├── FIELD-TYPE-GUARDS.md # Type guards for field discrimination
|
||||
├── COLLECTIONS.md # Collection patterns
|
||||
├── HOOKS.md # Hook patterns and examples
|
||||
├── ACCESS-CONTROL.md # Basic access control
|
||||
├── ACCESS-CONTROL-ADVANCED.md # Advanced access patterns
|
||||
├── QUERIES.md # Query patterns and APIs
|
||||
├── ENDPOINTS.md # Custom endpoints
|
||||
├── ADAPTERS.md # Database and storage adapters
|
||||
├── PLUGIN-DEVELOPMENT.md # Plugin development patterns
|
||||
└── ADVANCED.md # Jobs, endpoints, localization
|
||||
```
|
||||
|
||||
|
||||
+162
-53
@@ -9,35 +9,35 @@ Payload is a Next.js native CMS with TypeScript-first architecture, providing ad
|
||||
|
||||
## Quick Reference
|
||||
|
||||
| Task | Solution | Details |
|
||||
| ------------------------ | ----------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| Auto-generate slugs | `slugField()` | [FIELDS.md#slug-field-helper](reference/FIELDS.md#slug-field-helper) |
|
||||
| Restrict content by user | Access control with query | [ACCESS-CONTROL.md#row-level-security-with-complex-queries](reference/ACCESS-CONTROL.md#row-level-security-with-complex-queries) |
|
||||
| Local API user ops | `user` + `overrideAccess: false` | [QUERIES.md#access-control-in-local-api](reference/QUERIES.md#access-control-in-local-api) |
|
||||
| Draft/publish workflow | `versions: { drafts: true }` | [COLLECTIONS.md#versioning--drafts](reference/COLLECTIONS.md#versioning--drafts) |
|
||||
| Computed fields | `virtual: true` with afterRead | [FIELDS.md#virtual-fields](reference/FIELDS.md#virtual-fields) |
|
||||
| Conditional fields | `admin.condition` | [FIELDS.md#conditional-fields](reference/FIELDS.md#conditional-fields) |
|
||||
| Custom field validation | `validate` function | [FIELDS.md#text-field](reference/FIELDS.md#text-field) |
|
||||
| Filter relationship list | `filterOptions` on field | [FIELDS.md#relationship](reference/FIELDS.md#relationship) |
|
||||
| Select specific fields | `select` parameter | [QUERIES.md#local-api](reference/QUERIES.md#local-api) |
|
||||
| Auto-set author/dates | beforeChange hook | [HOOKS.md#collection-hooks](reference/HOOKS.md#collection-hooks) |
|
||||
| Prevent hook loops | `req.context` check | [HOOKS.md#hook-context](reference/HOOKS.md#hook-context) |
|
||||
| Cascading deletes | beforeDelete hook | [HOOKS.md#collection-hooks](reference/HOOKS.md#collection-hooks) |
|
||||
| Geospatial queries | `point` field with `near`/`within` | [FIELDS.md#point-geolocation](reference/FIELDS.md#point-geolocation) |
|
||||
| Reverse relationships | `join` field type | [FIELDS.md#join-fields](reference/FIELDS.md#join-fields) |
|
||||
| Next.js revalidation | Context control in afterChange | [HOOKS.md#nextjs-revalidation-with-context-control](reference/HOOKS.md#nextjs-revalidation-with-context-control) |
|
||||
| Query by relationship | Nested property syntax | [QUERIES.md#nested-properties](reference/QUERIES.md#nested-properties) |
|
||||
| Complex queries | AND/OR logic | [QUERIES.md#andor-logic](reference/QUERIES.md#andor-logic) |
|
||||
| Transactions | Pass `req` to operations | [ADAPTERS.md#threading-req-through-operations](reference/ADAPTERS.md#threading-req-through-operations) |
|
||||
| Background jobs | Jobs queue with tasks | [ADVANCED.md#jobs-queue](reference/ADVANCED.md#jobs-queue) |
|
||||
| Custom API routes | Collection custom endpoints | [ADVANCED.md#custom-endpoints](reference/ADVANCED.md#custom-endpoints) |
|
||||
| Cloud storage | Storage adapter plugins | [ADAPTERS.md#storage-adapters](reference/ADAPTERS.md#storage-adapters) |
|
||||
| Multi-language | `localization` config + `localized: true` | [ADVANCED.md#localization](reference/ADVANCED.md#localization) |
|
||||
| Create plugin | `(options) => (config) => Config` | [PLUGIN-DEVELOPMENT.md#plugin-architecture](reference/PLUGIN-DEVELOPMENT.md#plugin-architecture) |
|
||||
| Plugin package setup | Package structure with SWC | [PLUGIN-DEVELOPMENT.md#plugin-package-structure](reference/PLUGIN-DEVELOPMENT.md#plugin-package-structure) |
|
||||
| Add fields to collection | Map collections, spread fields | [PLUGIN-DEVELOPMENT.md#adding-fields-to-collections](reference/PLUGIN-DEVELOPMENT.md#adding-fields-to-collections) |
|
||||
| Plugin hooks | Preserve existing hooks in array | [PLUGIN-DEVELOPMENT.md#adding-hooks](reference/PLUGIN-DEVELOPMENT.md#adding-hooks) |
|
||||
| Check field type | Type guard functions | [FIELD-TYPE-GUARDS.md](reference/FIELD-TYPE-GUARDS.md) |
|
||||
| Task | Solution | Details |
|
||||
| ------------------------ | -------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| Auto-generate slugs | `slugField()` | [FIELDS.md#slug-field-helper](reference/FIELDS.md#slug-field-helper) |
|
||||
| Restrict content by user | Access control with query | [ACCESS-CONTROL.md#row-level-security-with-complex-queries](reference/ACCESS-CONTROL.md#row-level-security-with-complex-queries) |
|
||||
| Local API user ops | `user` + `overrideAccess: false` | [QUERIES.md#access-control-in-local-api](reference/QUERIES.md#access-control-in-local-api) |
|
||||
| Draft/publish workflow | `versions: { drafts: true }` | [COLLECTIONS.md#versioning--drafts](reference/COLLECTIONS.md#versioning--drafts) |
|
||||
| Computed fields | `virtual: true` with **field-level** `hooks.afterRead` returning the value | [FIELDS.md#virtual-fields](reference/FIELDS.md#virtual-fields) |
|
||||
| Conditional fields | `admin.condition` | [FIELDS.md#conditional-fields](reference/FIELDS.md#conditional-fields) |
|
||||
| Custom field validation | `validate` function | [FIELDS.md#validation](reference/FIELDS.md#validation) |
|
||||
| Filter relationship list | `filterOptions` on field | [FIELDS.md#relationship](reference/FIELDS.md#relationship) |
|
||||
| Select specific fields | `select` parameter | [QUERIES.md#field-selection](reference/QUERIES.md#field-selection) |
|
||||
| Auto-set author/dates | beforeChange hook | [HOOKS.md#collection-hooks](reference/HOOKS.md#collection-hooks) |
|
||||
| Prevent hook loops | `req.context` check | [HOOKS.md#context](reference/HOOKS.md#context) |
|
||||
| Cascading deletes | beforeDelete hook | [HOOKS.md#collection-hooks](reference/HOOKS.md#collection-hooks) |
|
||||
| Geospatial queries | `point` field with `near`/`within` | [FIELDS.md#point-geolocation](reference/FIELDS.md#point-geolocation) |
|
||||
| Reverse relationships | `join` field type | [FIELDS.md#join-fields](reference/FIELDS.md#join-fields) |
|
||||
| Next.js revalidation | Context control in afterChange | [HOOKS.md#nextjs-revalidation-with-context-control](reference/HOOKS.md#nextjs-revalidation-with-context-control) |
|
||||
| Query by relationship | Nested property syntax | [QUERIES.md#nested-properties](reference/QUERIES.md#nested-properties) |
|
||||
| Complex queries | AND/OR logic | [QUERIES.md#andor-logic](reference/QUERIES.md#andor-logic) |
|
||||
| Transactions | Pass `req` to operations | [ADAPTERS.md#threading-req-through-operations](reference/ADAPTERS.md#threading-req-through-operations) |
|
||||
| Background jobs | Jobs queue with tasks | [ADVANCED.md#jobs-queue](reference/ADVANCED.md#jobs-queue) |
|
||||
| Custom API routes | Collection custom endpoints | [ADVANCED.md#custom-endpoints](reference/ADVANCED.md#custom-endpoints) |
|
||||
| Cloud storage | Storage adapter plugins | [ADAPTERS.md#storage-adapters](reference/ADAPTERS.md#storage-adapters) |
|
||||
| Multi-language | `localization` config + `localized: true` | [ADVANCED.md#localization](reference/ADVANCED.md#localization) |
|
||||
| Create plugin | `(options) => (config) => Config` | [PLUGIN-DEVELOPMENT.md#plugin-architecture](reference/PLUGIN-DEVELOPMENT.md#plugin-architecture) |
|
||||
| Plugin package setup | Package structure with SWC | [PLUGIN-DEVELOPMENT.md#plugin-package-structure](reference/PLUGIN-DEVELOPMENT.md#plugin-package-structure) |
|
||||
| Add fields to collection | Map collections, spread fields | [PLUGIN-DEVELOPMENT.md#adding-fields-to-collections](reference/PLUGIN-DEVELOPMENT.md#adding-fields-to-collections) |
|
||||
| Plugin hooks | Preserve existing hooks in array | [PLUGIN-DEVELOPMENT.md#adding-hooks](reference/PLUGIN-DEVELOPMENT.md#adding-hooks) |
|
||||
| Check field type | Type guard functions | [FIELD-TYPE-GUARDS.md](reference/FIELD-TYPE-GUARDS.md) |
|
||||
|
||||
## Quick Start
|
||||
|
||||
@@ -80,22 +80,47 @@ export default buildConfig({
|
||||
|
||||
## Essential Patterns
|
||||
|
||||
### Defaults & Conventions
|
||||
|
||||
Apply these defaults when modeling content unless there's a clear reason not to:
|
||||
|
||||
- **Enable drafts/versions by default:** `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).
|
||||
- **Use `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.
|
||||
|
||||
### Basic Collection
|
||||
|
||||
```ts
|
||||
import type { CollectionConfig } from 'payload'
|
||||
import { slugField } from 'payload'
|
||||
|
||||
export const Posts: CollectionConfig = {
|
||||
slug: 'posts',
|
||||
admin: {
|
||||
useAsTitle: 'title',
|
||||
defaultColumns: ['title', 'author', 'status', 'createdAt'],
|
||||
// _status (from versions.drafts) shows the draft/published state — no custom status field needed
|
||||
defaultColumns: ['title', 'author', '_status', 'createdAt'],
|
||||
},
|
||||
versions: {
|
||||
drafts: true,
|
||||
},
|
||||
fields: [
|
||||
{ name: 'title', type: 'text', required: true },
|
||||
{ name: 'slug', type: 'text', unique: true, index: true },
|
||||
{ name: 'content', type: 'richText' },
|
||||
{ name: 'author', type: 'relationship', relationTo: 'users' },
|
||||
slugField(), // auto-generates from `title`, unique + indexed, sidebar position
|
||||
{ name: 'content', type: 'richText' }, // long field — stays in the main area, not the sidebar
|
||||
// short, at-a-glance field — good sidebar candidate
|
||||
{ name: 'author', type: 'relationship', relationTo: 'users', admin: { position: 'sidebar' } },
|
||||
],
|
||||
timestamps: true,
|
||||
}
|
||||
@@ -115,8 +140,11 @@ For more collection patterns (auth, upload, drafts, live preview), see [COLLECTI
|
||||
// Rich text
|
||||
{ name: 'content', type: 'richText', required: true }
|
||||
|
||||
// Select
|
||||
{ name: 'status', type: 'select', options: ['draft', 'published'], defaultValue: 'draft' }
|
||||
// Slug — use the helper instead of a hand-rolled text field
|
||||
slugField()
|
||||
|
||||
// Select (for genuine taxonomy — NOT publish state; use versions.drafts + _status for that)
|
||||
{ name: 'category', type: 'select', options: ['news', 'tutorial', 'opinion'] }
|
||||
|
||||
// Upload
|
||||
{ name: 'image', type: 'upload', relationTo: 'media' }
|
||||
@@ -126,7 +154,10 @@ For all field types (array, blocks, point, join, virtual, conditional, etc.), se
|
||||
|
||||
### Hook Example
|
||||
|
||||
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.
|
||||
|
||||
```ts
|
||||
// Collection-level: business logic across the document
|
||||
export const Posts: CollectionConfig = {
|
||||
slug: 'posts',
|
||||
hooks: {
|
||||
@@ -141,8 +172,27 @@ export const Posts: CollectionConfig = {
|
||||
},
|
||||
fields: [{ name: 'title', type: 'text' }],
|
||||
}
|
||||
|
||||
// Field-level: compute / format a single field's value (virtual fields use this)
|
||||
export const Users: CollectionConfig = {
|
||||
slug: 'users',
|
||||
fields: [
|
||||
{ name: 'firstName', type: 'text' },
|
||||
{ name: 'lastName', type: 'text' },
|
||||
{
|
||||
name: 'fullName',
|
||||
type: 'text',
|
||||
virtual: true,
|
||||
hooks: {
|
||||
afterRead: [({ siblingData }) => `${siblingData.firstName} ${siblingData.lastName}`],
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
```
|
||||
|
||||
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](reference/HOOKS.md). For access control, see [ACCESS-CONTROL.md](reference/ACCESS-CONTROL.md).
|
||||
|
||||
### Access Control with Type Safety
|
||||
@@ -232,22 +282,6 @@ export default async function Page() {
|
||||
}
|
||||
```
|
||||
|
||||
### Logger Usage
|
||||
|
||||
```ts
|
||||
// ✅ Valid: single string
|
||||
payload.logger.error('Something went wrong')
|
||||
|
||||
// ✅ Valid: object with msg and err
|
||||
payload.logger.error({ msg: 'Failed to process', err: error })
|
||||
|
||||
// ❌ Invalid: don't pass error as second argument
|
||||
payload.logger.error('Failed to process', error)
|
||||
|
||||
// ❌ Invalid: use `err` not `error`, use `msg` not `message`
|
||||
payload.logger.error({ message: 'Failed', error: error })
|
||||
```
|
||||
|
||||
## Security Pitfalls
|
||||
|
||||
### 1. Local API Access Control (CRITICAL)
|
||||
@@ -371,21 +405,96 @@ src/
|
||||
└── payload.config.ts
|
||||
```
|
||||
|
||||
## Type Generation
|
||||
## Building & Type Generation
|
||||
|
||||
Payload generates `payload-types.ts` for you — you rarely need to run `generate:types` by hand.
|
||||
|
||||
- **During development:** `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.
|
||||
- **During builds:** `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.
|
||||
- **Manual 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`).
|
||||
|
||||
```ts
|
||||
// payload.config.ts
|
||||
export default buildConfig({
|
||||
typescript: {
|
||||
outputFile: path.resolve(dirname, 'payload-types.ts'),
|
||||
// autoGenerate defaults to true — types regenerate in dev automatically
|
||||
},
|
||||
// ...
|
||||
})
|
||||
|
||||
// Usage
|
||||
import type { Post, User } from '@/payload-types'
|
||||
```
|
||||
|
||||
## Common Gotchas
|
||||
|
||||
1. **Local API bypasses access control** unless you pass `overrideAccess: false`
|
||||
2. **Missing `req` in nested operations** breaks transaction atomicity
|
||||
3. **Hook loops** — operations in hooks can re-trigger the same hooks; use `req.context` flags
|
||||
4. **Field-level access** returns boolean only, no query constraints
|
||||
5. **Relationship depth** defaults to 2; set `depth: 0` for IDs only
|
||||
6. **Draft status** — `_status` field is auto-injected when drafts are enabled
|
||||
7. **Types regenerate automatically** in dev (`autoGenerate`) and during `payload build` — avoid running `generate:types` manually
|
||||
8. **MongoDB transactions** require replica set configuration
|
||||
9. **SQLite transactions** are disabled by default; enable with `transactionOptions: {}`
|
||||
10. **Point fields** are not supported in SQLite
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Content Modeling
|
||||
|
||||
- Enable `versions: { drafts: true }` by default on content collections; rely on the
|
||||
auto-injected `_status` field rather than adding a custom `status` field
|
||||
- Use `slugField()` for slugs instead of hand-rolling a unique text field
|
||||
- Reserve `position: 'sidebar'` for short, at-a-glance fields (status, category,
|
||||
author, date); keep long fields (description, rich text) in the main area
|
||||
|
||||
### Security
|
||||
|
||||
- Default to restrictive access, gradually add permissions
|
||||
- Use `overrideAccess: false` when passing `user` to Local API
|
||||
- Field-level access only returns boolean (no query constraints)
|
||||
- Never trust client-provided data
|
||||
- Use `saveToJWT: true` for roles to avoid database lookups
|
||||
|
||||
### Performance
|
||||
|
||||
- Index frequently queried fields
|
||||
- Use `select` to limit returned fields
|
||||
- Set `maxDepth` on relationships to prevent over-fetching
|
||||
- Prefer query constraints over async operations in access control
|
||||
- Cache expensive operations in `req.context`
|
||||
|
||||
### Data Integrity
|
||||
|
||||
- Always pass `req` to nested operations in hooks
|
||||
- Use context flags to prevent infinite hook loops
|
||||
- Enable transactions for MongoDB (requires replica set) and Postgres
|
||||
- Use `beforeValidate` for data formatting
|
||||
- Use `beforeChange` for business logic
|
||||
|
||||
### Type Safety
|
||||
|
||||
- Let dev (`autoGenerate`) and `payload build` generate types; run `generate:types` manually only when neither is running
|
||||
- Import types from generated `payload-types.ts`
|
||||
- Type your user object: `import type { User } from '@/payload-types'`
|
||||
- Use field type guards for runtime type checking
|
||||
- When extracting any Payload value into a named constant — a collection, field, hook, access function, plugin, etc. — annotate it with the matching Payload type (`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.
|
||||
|
||||
### Organization
|
||||
|
||||
- Keep collections in separate files
|
||||
- Extract access control to `access/` directory
|
||||
- Extract hooks to `hooks/` directory
|
||||
- Use reusable field factories for common patterns
|
||||
- Document complex access control with comments
|
||||
|
||||
## Reference Documentation
|
||||
|
||||
- **[FIELDS.md](reference/FIELDS.md)** - All field types, validation, admin options
|
||||
|
||||
@@ -85,7 +85,6 @@ export const Posts: CollectionConfig = {
|
||||
},
|
||||
fields: [
|
||||
{ name: 'title', type: 'text' },
|
||||
{ name: 'status', type: 'select', options: ['draft', 'published'] },
|
||||
{ name: 'author', type: 'relationship', relationTo: 'users' },
|
||||
],
|
||||
}
|
||||
|
||||
@@ -236,7 +236,7 @@ export const CustomField: TextFieldClientComponent = () => {
|
||||
|
||||
```tsx
|
||||
'use client'
|
||||
import { DefaultTemplate } from '@payloadcms/next/templates'
|
||||
import { DefaultTemplate } from '@payloadcms/ui/rsc'
|
||||
|
||||
export const CustomView = () => {
|
||||
return (
|
||||
|
||||
@@ -6,6 +6,7 @@ Complete reference for collection configurations and patterns.
|
||||
|
||||
```ts
|
||||
import type { CollectionConfig } from 'payload'
|
||||
import { slugField } from 'payload'
|
||||
|
||||
export const Posts: CollectionConfig = {
|
||||
slug: 'posts',
|
||||
@@ -15,11 +16,16 @@ export const Posts: CollectionConfig = {
|
||||
},
|
||||
admin: {
|
||||
useAsTitle: 'title',
|
||||
defaultColumns: ['title', 'author', 'status', 'createdAt'],
|
||||
// _status comes from versions.drafts below — no custom status field needed
|
||||
defaultColumns: ['title', 'author', '_status', 'createdAt'],
|
||||
group: 'Content', // Organize in admin sidebar
|
||||
description: 'Blog posts and articles',
|
||||
listSearchableFields: ['title', 'slug'],
|
||||
},
|
||||
// Enable drafts by default — auto-injects the _status field (draft/published/changed)
|
||||
versions: {
|
||||
drafts: true,
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'title',
|
||||
@@ -27,25 +33,18 @@ export const Posts: CollectionConfig = {
|
||||
required: true,
|
||||
index: true,
|
||||
},
|
||||
{
|
||||
name: 'slug',
|
||||
type: 'text',
|
||||
unique: true,
|
||||
index: true,
|
||||
admin: { position: 'sidebar' },
|
||||
},
|
||||
{
|
||||
name: 'status',
|
||||
type: 'select',
|
||||
options: ['draft', 'published'],
|
||||
defaultValue: 'draft',
|
||||
},
|
||||
slugField(), // unique + indexed, sidebar position — don't hand-roll a slug text field
|
||||
],
|
||||
defaultSort: '-createdAt',
|
||||
timestamps: true,
|
||||
}
|
||||
```
|
||||
|
||||
> Don't add a custom `status` select for publish state — enabling
|
||||
> `versions: { drafts: true }` injects a managed `_status` field
|
||||
> (`draft` / `published` / `changed`) that the admin UI and Draft Preview already
|
||||
> understand. Use it in `defaultColumns` and access control directly.
|
||||
|
||||
## Auth Collection
|
||||
|
||||
```ts
|
||||
@@ -129,6 +128,7 @@ Enable real-time content preview during editing.
|
||||
|
||||
```ts
|
||||
import type { CollectionConfig } from 'payload'
|
||||
import { slugField } from 'payload'
|
||||
|
||||
const generatePreviewPath = ({
|
||||
slug,
|
||||
@@ -164,10 +164,7 @@ export const Pages: CollectionConfig = {
|
||||
req,
|
||||
}),
|
||||
},
|
||||
fields: [
|
||||
{ name: 'title', type: 'text' },
|
||||
{ name: 'slug', type: 'text' },
|
||||
],
|
||||
fields: [{ name: 'title', type: 'text' }, slugField()],
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -26,9 +26,19 @@ const textField: TextField = {
|
||||
}
|
||||
```
|
||||
|
||||
> **When to use `position: 'sidebar'`:** Reserve the sidebar for short fields that
|
||||
> give quick insight into the content — status, category, author, publish date,
|
||||
> slug. Avoid it for fields that need horizontal space to be useful, like a
|
||||
> description, rich text content, or long text — those belong in the main document
|
||||
> area. (`title` above is shown in the sidebar only to demonstrate the option.)
|
||||
|
||||
### Slug Field Helper
|
||||
|
||||
Built-in helper for auto-generating slugs:
|
||||
Built-in helper for auto-generating slugs. **Use this for all slugs** instead of
|
||||
hand-rolling a `{ name: 'slug', type: 'text', unique: true }` field — it
|
||||
auto-generates the slug from the title, adds a regenerate toggle, and handles
|
||||
uniqueness and indexing. Call it with no args when the collection has a `title`
|
||||
field — the slug is generated from `title` by default:
|
||||
|
||||
```ts
|
||||
import { slugField } from 'payload'
|
||||
@@ -38,21 +48,37 @@ export const Pages: CollectionConfig = {
|
||||
slug: 'pages',
|
||||
fields: [
|
||||
{ name: 'title', type: 'text', required: true },
|
||||
slugField({
|
||||
name: 'slug', // defaults to 'slug'
|
||||
useAsSlug: 'title', // defaults to 'title'
|
||||
checkboxName: 'generateSlug', // defaults to 'generateSlug'
|
||||
localized: true,
|
||||
required: true,
|
||||
overrides: (defaultField) => {
|
||||
// Customize the generated fields if needed
|
||||
return defaultField
|
||||
},
|
||||
}),
|
||||
slugField(), // name: 'slug', useAsSlug: 'title', required, unique, position: 'sidebar'
|
||||
],
|
||||
}
|
||||
```
|
||||
|
||||
`useAsSlug` defaults to `'title'`, so if the collection has **no `title` field**,
|
||||
you must pass the source field explicitly — otherwise the slug generates from a
|
||||
field that doesn't exist:
|
||||
|
||||
```ts
|
||||
// Collection keyed on `name` instead of `title`
|
||||
fields: [{ name: 'name', type: 'text', required: true }, slugField({ useAsSlug: 'name' })]
|
||||
```
|
||||
|
||||
Override defaults when needed (`overrides` receives the generated `RowField`):
|
||||
|
||||
```ts
|
||||
slugField({
|
||||
name: 'slug', // defaults to 'slug'
|
||||
useAsSlug: 'title', // defaults to 'title'
|
||||
checkboxName: 'generateSlug', // defaults to 'generateSlug'
|
||||
localized: true,
|
||||
required: true, // defaults to true
|
||||
position: 'sidebar', // default; the slug is a short field well-suited to the sidebar
|
||||
overrides: (field) => {
|
||||
field.fields[1].label = 'Custom Slug Label'
|
||||
return field
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
## Rich Text (Lexical)
|
||||
|
||||
```ts
|
||||
@@ -264,14 +290,17 @@ const blocksField: BlocksField = {
|
||||
```ts
|
||||
import type { SelectField } from 'payload'
|
||||
|
||||
// Use select for genuine taxonomy. For publish state, enable versions.drafts
|
||||
// and rely on the auto-injected _status field instead of a custom select.
|
||||
const selectField: SelectField = {
|
||||
name: 'status',
|
||||
name: 'priority',
|
||||
type: 'select',
|
||||
options: [
|
||||
{ label: 'Draft', value: 'draft' },
|
||||
{ label: 'Published', value: 'published' },
|
||||
{ label: 'Low', value: 'low' },
|
||||
{ label: 'Medium', value: 'medium' },
|
||||
{ label: 'High', value: 'high' },
|
||||
],
|
||||
defaultValue: 'draft',
|
||||
defaultValue: 'medium',
|
||||
required: true,
|
||||
}
|
||||
|
||||
|
||||
@@ -147,15 +147,15 @@ plugin-<name>/
|
||||
"@swc/cli": "^0.1.62",
|
||||
"@swc/core": "^1.3.0",
|
||||
"copyfiles": "^2.4.1",
|
||||
"cross-env": "^7.0.3",
|
||||
"cross-env": "10.1.0",
|
||||
"eslint": "^9.0.0",
|
||||
"next": "^15.4.10",
|
||||
"payload": "^3.0.0",
|
||||
"react": "^19.2.1",
|
||||
"react-dom": "^19.2.1",
|
||||
"rimraf": "^5.0.0",
|
||||
"typescript": "^5.0.0",
|
||||
"vitest": "^3.0.0"
|
||||
"typescript": "^6.0.0",
|
||||
"vitest": "4.1.6"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"payload": "^3.0.0"
|
||||
|
||||
Reference in New Issue
Block a user