Add configuration for BMad, Claude, OpenCode, and other AI agent tools and workflows.
313 lines
6.4 KiB
Markdown
313 lines
6.4 KiB
Markdown
# Collections Reference
|
|
|
|
## Basic Collection Config
|
|
|
|
```ts
|
|
import type { CollectionConfig } from 'payload'
|
|
|
|
export const Posts: CollectionConfig = {
|
|
slug: 'posts',
|
|
admin: {
|
|
useAsTitle: 'title',
|
|
defaultColumns: ['title', 'author', 'status', 'createdAt'],
|
|
group: 'Content', // Groups in sidebar
|
|
},
|
|
fields: [...],
|
|
timestamps: true, // Adds createdAt, updatedAt
|
|
}
|
|
```
|
|
|
|
## Auth Collection
|
|
|
|
Enable authentication on a collection:
|
|
|
|
```ts
|
|
export const Users: CollectionConfig = {
|
|
slug: 'users',
|
|
auth: {
|
|
tokenExpiration: 7200, // 2 hours
|
|
verify: true, // Email verification
|
|
maxLoginAttempts: 5,
|
|
lockTime: 600 * 1000, // 10 min lockout
|
|
},
|
|
fields: [
|
|
{ name: 'name', type: 'text', required: true },
|
|
{
|
|
name: 'roles',
|
|
type: 'select',
|
|
hasMany: true,
|
|
options: ['admin', 'editor', 'user'],
|
|
defaultValue: ['user'],
|
|
},
|
|
],
|
|
}
|
|
```
|
|
|
|
## Upload Collection
|
|
|
|
Handle file uploads:
|
|
|
|
```ts
|
|
export const Media: CollectionConfig = {
|
|
slug: 'media',
|
|
upload: {
|
|
staticDir: 'media',
|
|
mimeTypes: ['image/*', 'application/pdf'],
|
|
imageSizes: [
|
|
{ name: 'thumbnail', width: 400, height: 300, position: 'centre' },
|
|
{ name: 'card', width: 768, height: 1024, position: 'centre' },
|
|
],
|
|
adminThumbnail: 'thumbnail',
|
|
},
|
|
fields: [
|
|
{ name: 'alt', type: 'text', required: true },
|
|
{ name: 'caption', type: 'textarea' },
|
|
],
|
|
}
|
|
```
|
|
|
|
## Versioning & Drafts
|
|
|
|
Enable draft/publish workflow:
|
|
|
|
```ts
|
|
export const Posts: CollectionConfig = {
|
|
slug: 'posts',
|
|
versions: {
|
|
drafts: true,
|
|
maxPerDoc: 10, // Keep last 10 versions
|
|
},
|
|
fields: [...],
|
|
}
|
|
```
|
|
|
|
Query drafts:
|
|
|
|
```ts
|
|
// Get published only (default)
|
|
await payload.find({ collection: 'posts' })
|
|
|
|
// Include drafts
|
|
await payload.find({ collection: 'posts', draft: true })
|
|
```
|
|
|
|
## Live Preview
|
|
|
|
Real-time preview for frontend:
|
|
|
|
```ts
|
|
export const Pages: CollectionConfig = {
|
|
slug: 'pages',
|
|
admin: {
|
|
livePreview: {
|
|
url: ({ data }) => `${process.env.NEXT_PUBLIC_URL}/preview/${data.slug}`,
|
|
},
|
|
},
|
|
versions: { drafts: true },
|
|
fields: [...],
|
|
}
|
|
```
|
|
|
|
## Access Control
|
|
|
|
```ts
|
|
export const Posts: CollectionConfig = {
|
|
slug: 'posts',
|
|
access: {
|
|
create: ({ req }) => !!req.user, // Logged in users
|
|
read: () => true, // Public read
|
|
update: ({ req }) => req.user?.roles?.includes('admin'),
|
|
delete: ({ req }) => req.user?.roles?.includes('admin'),
|
|
},
|
|
fields: [...],
|
|
}
|
|
```
|
|
|
|
## Hooks Configuration
|
|
|
|
```ts
|
|
export const Posts: CollectionConfig = {
|
|
slug: 'posts',
|
|
hooks: {
|
|
beforeValidate: [...],
|
|
beforeChange: [...],
|
|
afterChange: [...],
|
|
beforeRead: [...],
|
|
afterRead: [...],
|
|
beforeDelete: [...],
|
|
afterDelete: [...],
|
|
// Auth-only hooks
|
|
afterLogin: [...],
|
|
afterLogout: [...],
|
|
afterMe: [...],
|
|
afterRefresh: [...],
|
|
afterForgotPassword: [...],
|
|
},
|
|
fields: [...],
|
|
}
|
|
```
|
|
|
|
## Custom Endpoints
|
|
|
|
Add API routes to a collection:
|
|
|
|
```ts
|
|
export const Posts: CollectionConfig = {
|
|
slug: 'posts',
|
|
endpoints: [
|
|
{
|
|
path: '/publish/:id',
|
|
method: 'post',
|
|
handler: async (req) => {
|
|
const { id } = req.routeParams
|
|
await req.payload.update({
|
|
collection: 'posts',
|
|
id,
|
|
data: { status: 'published', publishedAt: new Date() },
|
|
req,
|
|
})
|
|
return Response.json({ success: true })
|
|
},
|
|
},
|
|
],
|
|
fields: [...],
|
|
}
|
|
```
|
|
|
|
## Admin Panel Options
|
|
|
|
```ts
|
|
export const Posts: CollectionConfig = {
|
|
slug: 'posts',
|
|
admin: {
|
|
useAsTitle: 'title',
|
|
defaultColumns: ['title', 'status', 'createdAt'],
|
|
group: 'Content',
|
|
description: 'Manage blog posts',
|
|
hidden: false, // Hide from sidebar
|
|
listSearchableFields: ['title', 'slug'],
|
|
pagination: {
|
|
defaultLimit: 20,
|
|
limits: [10, 20, 50, 100],
|
|
},
|
|
preview: (doc) => `${process.env.NEXT_PUBLIC_URL}/${doc.slug}`,
|
|
},
|
|
fields: [...],
|
|
}
|
|
```
|
|
|
|
## Labels & Localization
|
|
|
|
```ts
|
|
export const Posts: CollectionConfig = {
|
|
slug: 'posts',
|
|
labels: {
|
|
singular: 'Article',
|
|
plural: 'Articles',
|
|
},
|
|
fields: [...],
|
|
}
|
|
```
|
|
|
|
## Database Indexes
|
|
|
|
```ts
|
|
export const Posts: CollectionConfig = {
|
|
slug: 'posts',
|
|
fields: [
|
|
{ name: 'slug', type: 'text', unique: true, index: true },
|
|
{ name: 'publishedAt', type: 'date', index: true },
|
|
],
|
|
// Compound indexes via dbName
|
|
dbName: 'posts',
|
|
}
|
|
```
|
|
|
|
## Disable Operations
|
|
|
|
```ts
|
|
export const AuditLogs: CollectionConfig = {
|
|
slug: 'audit-logs',
|
|
admin: {
|
|
enableRichTextRelationship: false,
|
|
},
|
|
disableDuplicate: true, // No duplicate button
|
|
fields: [...],
|
|
}
|
|
```
|
|
|
|
## Full Example
|
|
|
|
```ts
|
|
import type { CollectionConfig } from 'payload'
|
|
import { slugField } from './fields/slugField'
|
|
|
|
export const Posts: CollectionConfig = {
|
|
slug: 'posts',
|
|
admin: {
|
|
useAsTitle: 'title',
|
|
defaultColumns: ['title', 'author', 'status', 'publishedAt'],
|
|
group: 'Content',
|
|
livePreview: {
|
|
url: ({ data }) => `${process.env.NEXT_PUBLIC_URL}/posts/${data.slug}`,
|
|
},
|
|
},
|
|
access: {
|
|
create: ({ req }) => !!req.user,
|
|
read: ({ req }) => {
|
|
if (req.user?.roles?.includes('admin')) return true
|
|
return { status: { equals: 'published' } }
|
|
},
|
|
update: ({ req }) => {
|
|
if (req.user?.roles?.includes('admin')) return true
|
|
return { author: { equals: req.user?.id } }
|
|
},
|
|
delete: ({ req }) => req.user?.roles?.includes('admin'),
|
|
},
|
|
versions: {
|
|
drafts: true,
|
|
maxPerDoc: 10,
|
|
},
|
|
hooks: {
|
|
beforeChange: [
|
|
async ({ data, operation }) => {
|
|
if (operation === 'create') {
|
|
data.slug = data.title?.toLowerCase().replace(/\s+/g, '-')
|
|
}
|
|
if (data.status === 'published' && !data.publishedAt) {
|
|
data.publishedAt = new Date()
|
|
}
|
|
return data
|
|
},
|
|
],
|
|
},
|
|
fields: [
|
|
{ name: 'title', type: 'text', required: true },
|
|
{ name: 'slug', type: 'text', unique: true, index: true },
|
|
{ name: 'content', type: 'richText', required: true },
|
|
{
|
|
name: 'author',
|
|
type: 'relationship',
|
|
relationTo: 'users',
|
|
required: true,
|
|
defaultValue: ({ user }) => user?.id,
|
|
},
|
|
{
|
|
name: 'status',
|
|
type: 'select',
|
|
options: ['draft', 'published', 'archived'],
|
|
defaultValue: 'draft',
|
|
},
|
|
{ name: 'publishedAt', type: 'date' },
|
|
{ name: 'featuredImage', type: 'upload', relationTo: 'media' },
|
|
{
|
|
name: 'categories',
|
|
type: 'relationship',
|
|
relationTo: 'categories',
|
|
hasMany: true,
|
|
},
|
|
],
|
|
timestamps: true,
|
|
}
|
|
```
|