301 lines
6.2 KiB
Markdown
301 lines
6.2 KiB
Markdown
|
|
# Payload Collections Reference
|
||
|
|
|
||
|
|
Complete reference for collection configurations and patterns.
|
||
|
|
|
||
|
|
## Basic Collection
|
||
|
|
|
||
|
|
```ts
|
||
|
|
import type { CollectionConfig } from 'payload'
|
||
|
|
import { slugField } from 'payload'
|
||
|
|
|
||
|
|
export const Posts: CollectionConfig = {
|
||
|
|
slug: 'posts',
|
||
|
|
labels: {
|
||
|
|
singular: 'Post',
|
||
|
|
plural: 'Posts',
|
||
|
|
},
|
||
|
|
admin: {
|
||
|
|
useAsTitle: 'title',
|
||
|
|
// _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',
|
||
|
|
type: 'text',
|
||
|
|
required: true,
|
||
|
|
index: true,
|
||
|
|
},
|
||
|
|
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
|
||
|
|
export const Users: CollectionConfig = {
|
||
|
|
slug: 'users',
|
||
|
|
auth: {
|
||
|
|
tokenExpiration: 7200, // 2 hours
|
||
|
|
verify: true,
|
||
|
|
maxLoginAttempts: 5,
|
||
|
|
lockTime: 600000, // 10 minutes
|
||
|
|
useAPIKey: true,
|
||
|
|
},
|
||
|
|
admin: {
|
||
|
|
useAsTitle: 'email',
|
||
|
|
},
|
||
|
|
fields: [
|
||
|
|
{
|
||
|
|
name: 'roles',
|
||
|
|
type: 'select',
|
||
|
|
hasMany: true,
|
||
|
|
options: ['admin', 'editor', 'user'],
|
||
|
|
required: true,
|
||
|
|
defaultValue: ['user'],
|
||
|
|
saveToJWT: true,
|
||
|
|
},
|
||
|
|
{
|
||
|
|
name: 'name',
|
||
|
|
type: 'text',
|
||
|
|
required: true,
|
||
|
|
},
|
||
|
|
],
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
## Upload Collection
|
||
|
|
|
||
|
|
```ts
|
||
|
|
export const Media: CollectionConfig = {
|
||
|
|
slug: 'media',
|
||
|
|
upload: {
|
||
|
|
staticDir: 'media',
|
||
|
|
mimeTypes: ['image/*'],
|
||
|
|
imageSizes: [
|
||
|
|
{
|
||
|
|
name: 'thumbnail',
|
||
|
|
width: 400,
|
||
|
|
height: 300,
|
||
|
|
position: 'centre',
|
||
|
|
},
|
||
|
|
{
|
||
|
|
name: 'card',
|
||
|
|
width: 768,
|
||
|
|
height: 1024,
|
||
|
|
},
|
||
|
|
],
|
||
|
|
adminThumbnail: 'thumbnail',
|
||
|
|
focalPoint: true,
|
||
|
|
crop: true,
|
||
|
|
},
|
||
|
|
access: {
|
||
|
|
read: () => true,
|
||
|
|
},
|
||
|
|
fields: [
|
||
|
|
{
|
||
|
|
name: 'alt',
|
||
|
|
type: 'text',
|
||
|
|
required: true,
|
||
|
|
},
|
||
|
|
{
|
||
|
|
name: 'caption',
|
||
|
|
type: 'text',
|
||
|
|
localized: true,
|
||
|
|
},
|
||
|
|
],
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
## Live Preview
|
||
|
|
|
||
|
|
Enable real-time content preview during editing.
|
||
|
|
|
||
|
|
```ts
|
||
|
|
import type { CollectionConfig } from 'payload'
|
||
|
|
import { slugField } from 'payload'
|
||
|
|
|
||
|
|
const generatePreviewPath = ({
|
||
|
|
slug,
|
||
|
|
collection,
|
||
|
|
req,
|
||
|
|
}: {
|
||
|
|
slug: string
|
||
|
|
collection: string
|
||
|
|
req: any
|
||
|
|
}) => {
|
||
|
|
const baseUrl = process.env.NEXT_PUBLIC_SERVER_URL
|
||
|
|
return `${baseUrl}/api/preview?slug=${slug}&collection=${collection}`
|
||
|
|
}
|
||
|
|
|
||
|
|
export const Pages: CollectionConfig = {
|
||
|
|
slug: 'pages',
|
||
|
|
admin: {
|
||
|
|
useAsTitle: 'title',
|
||
|
|
// Live preview during editing
|
||
|
|
livePreview: {
|
||
|
|
url: ({ data, req }) =>
|
||
|
|
generatePreviewPath({
|
||
|
|
slug: data?.slug as string,
|
||
|
|
collection: 'pages',
|
||
|
|
req,
|
||
|
|
}),
|
||
|
|
},
|
||
|
|
// Static preview button
|
||
|
|
preview: (data, { req }) =>
|
||
|
|
generatePreviewPath({
|
||
|
|
slug: data?.slug as string,
|
||
|
|
collection: 'pages',
|
||
|
|
req,
|
||
|
|
}),
|
||
|
|
},
|
||
|
|
fields: [{ name: 'title', type: 'text' }, slugField()],
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
## Versioning & Drafts
|
||
|
|
|
||
|
|
Payload maintains version history and supports draft/publish workflows.
|
||
|
|
|
||
|
|
```ts
|
||
|
|
import type { CollectionConfig } from 'payload'
|
||
|
|
|
||
|
|
// Basic versioning (audit log only)
|
||
|
|
export const Users: CollectionConfig = {
|
||
|
|
slug: 'users',
|
||
|
|
versions: true, // or { maxPerDoc: 100 }
|
||
|
|
fields: [{ name: 'name', type: 'text' }],
|
||
|
|
}
|
||
|
|
|
||
|
|
// Drafts enabled (draft/publish workflow)
|
||
|
|
export const Posts: CollectionConfig = {
|
||
|
|
slug: 'posts',
|
||
|
|
versions: {
|
||
|
|
drafts: true, // Enables _status field
|
||
|
|
maxPerDoc: 50,
|
||
|
|
},
|
||
|
|
fields: [{ name: 'title', type: 'text' }],
|
||
|
|
}
|
||
|
|
|
||
|
|
// Full configuration with autosave and scheduled publish
|
||
|
|
export const Pages: CollectionConfig = {
|
||
|
|
slug: 'pages',
|
||
|
|
versions: {
|
||
|
|
drafts: {
|
||
|
|
autosave: true, // Auto-save while editing
|
||
|
|
schedulePublish: true, // Schedule future publish/unpublish
|
||
|
|
validate: false, // Don't validate drafts (default)
|
||
|
|
},
|
||
|
|
maxPerDoc: 100, // Keep last 100 versions (0 = unlimited)
|
||
|
|
},
|
||
|
|
fields: [{ name: 'title', type: 'text' }],
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### Draft API Usage
|
||
|
|
|
||
|
|
```ts
|
||
|
|
// Create draft
|
||
|
|
await payload.create({
|
||
|
|
collection: 'posts',
|
||
|
|
data: { title: 'Draft Post' },
|
||
|
|
draft: true, // Saves as draft, skips required field validation
|
||
|
|
})
|
||
|
|
|
||
|
|
// Update as draft
|
||
|
|
await payload.update({
|
||
|
|
collection: 'posts',
|
||
|
|
id: '123',
|
||
|
|
data: { title: 'Updated Draft' },
|
||
|
|
draft: true,
|
||
|
|
})
|
||
|
|
|
||
|
|
// Read with drafts (returns newest draft if available)
|
||
|
|
const post = await payload.findByID({
|
||
|
|
collection: 'posts',
|
||
|
|
id: '123',
|
||
|
|
draft: true, // Returns draft version if exists
|
||
|
|
})
|
||
|
|
|
||
|
|
// Query only published (REST API)
|
||
|
|
// GET /api/posts (returns only _status: 'published')
|
||
|
|
|
||
|
|
// Access control for drafts
|
||
|
|
export const Posts: CollectionConfig = {
|
||
|
|
slug: 'posts',
|
||
|
|
versions: { drafts: true },
|
||
|
|
access: {
|
||
|
|
read: ({ req: { user } }) => {
|
||
|
|
// Public can only see published
|
||
|
|
if (!user) return { _status: { equals: 'published' } }
|
||
|
|
// Authenticated can see all
|
||
|
|
return true
|
||
|
|
},
|
||
|
|
},
|
||
|
|
fields: [{ name: 'title', type: 'text' }],
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### Document Status
|
||
|
|
|
||
|
|
The `_status` field is auto-injected when drafts are enabled:
|
||
|
|
|
||
|
|
- `draft` - Never published
|
||
|
|
- `published` - Published with no newer drafts
|
||
|
|
- `changed` - Published but has newer unpublished drafts
|
||
|
|
|
||
|
|
## Globals
|
||
|
|
|
||
|
|
Globals are single-instance documents (not collections).
|
||
|
|
|
||
|
|
```ts
|
||
|
|
import type { GlobalConfig } from 'payload'
|
||
|
|
|
||
|
|
export const Header: GlobalConfig = {
|
||
|
|
slug: 'header',
|
||
|
|
label: 'Header',
|
||
|
|
admin: {
|
||
|
|
group: 'Settings',
|
||
|
|
},
|
||
|
|
fields: [
|
||
|
|
{
|
||
|
|
name: 'logo',
|
||
|
|
type: 'upload',
|
||
|
|
relationTo: 'media',
|
||
|
|
required: true,
|
||
|
|
},
|
||
|
|
{
|
||
|
|
name: 'nav',
|
||
|
|
type: 'array',
|
||
|
|
maxRows: 8,
|
||
|
|
fields: [
|
||
|
|
{
|
||
|
|
name: 'link',
|
||
|
|
type: 'relationship',
|
||
|
|
relationTo: 'pages',
|
||
|
|
},
|
||
|
|
{
|
||
|
|
name: 'label',
|
||
|
|
type: 'text',
|
||
|
|
},
|
||
|
|
],
|
||
|
|
},
|
||
|
|
],
|
||
|
|
}
|
||
|
|
```
|