Files
website-enchun-mgr/.agent/skills/payload-cms/references/collections.md
pkupuk ad8e2e313e chore(agent): configure AI agents and tools
Add configuration for BMad, Claude, OpenCode, and other AI agent tools and workflows.
2026-02-11 11:51:23 +08:00

6.4 KiB

Collections Reference

Basic Collection Config

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:

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:

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:

export const Posts: CollectionConfig = {
  slug: 'posts',
  versions: {
    drafts: true,
    maxPerDoc: 10, // Keep last 10 versions
  },
  fields: [...],
}

Query drafts:

// 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:

export const Pages: CollectionConfig = {
  slug: 'pages',
  admin: {
    livePreview: {
      url: ({ data }) => `${process.env.NEXT_PUBLIC_URL}/preview/${data.slug}`,
    },
  },
  versions: { drafts: true },
  fields: [...],
}

Access Control

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

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:

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

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

export const Posts: CollectionConfig = {
  slug: 'posts',
  labels: {
    singular: 'Article',
    plural: 'Articles',
  },
  fields: [...],
}

Database Indexes

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

export const AuditLogs: CollectionConfig = {
  slug: 'audit-logs',
  admin: {
    enableRichTextRelationship: false,
  },
  disableDuplicate: true, // No duplicate button
  fields: [...],
}

Full Example

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