Claude Code for Payload CMS — Workflow Guide
The Setup
You are building a content management system with Payload CMS, the TypeScript-first headless CMS that gives you a complete admin panel, REST and GraphQL APIs, authentication, and access control — all generated from collection configs. Claude Code can generate Payload collections and hooks, but it creates raw Express CRUD routes instead of using Payload’s config-driven approach.
What Claude Code Gets Wrong By Default
-
Builds CRUD endpoints manually. Claude creates Express routes with Prisma queries. Payload auto-generates REST, GraphQL, and local APIs from collection definitions — you define the schema and Payload handles everything.
-
Creates separate auth systems. Claude builds JWT auth with bcrypt. Payload has built-in authentication — add
auth: trueto a collection and it provides login, registration, password reset, and session management. -
Uses Mongoose schema syntax. Claude writes Mongoose
new Schema({})definitions. Payload uses its own field types:{ name: 'title', type: 'text', required: true }in collection configs. Payload handles the database layer. -
Ignores Payload’s hook system. Claude writes Express middleware for custom logic. Payload has typed hooks (
beforeChange,afterRead,beforeValidate) on every collection for custom business logic.
The CLAUDE.md Configuration
# Payload CMS Project
## CMS
- Platform: Payload CMS 3.x (TypeScript headless CMS)
- Config: payload.config.ts at project root
- Collections: src/collections/ directory
- Database: PostgreSQL with @payloadcms/db-postgres
## Payload Rules
- Collections defined as config objects with fields array
- Field types: text, number, richText, upload, relationship, array, group
- Auth collection: add auth: true for built-in authentication
- Access control: per-collection and per-field access functions
- Hooks: beforeChange, afterChange, beforeRead, afterRead per collection
- Admin panel auto-generated at /admin
- API auto-generated at /api/collection-slug
- Never create manual CRUD routes — Payload handles them
## Conventions
- One file per collection: src/collections/Users.ts, Posts.ts
- Globals for site-wide settings: src/globals/SiteSettings.ts
- Hooks for business logic, not Express middleware
- Access control functions for permissions
- Custom components in src/components/ for admin panel
- Media uploads configured with upload: { ... } field type
- Blocks for flexible page building (layout builder pattern)
Workflow Example
You want to create a blog with categories and media uploads. Prompt Claude Code:
“Create Payload CMS collections for blog posts, categories, and media. Posts should have a title, rich text content, featured image (upload), categories (relationship), author (relationship to users), and publish date. Add access control so only logged-in users can create posts.”
Claude Code should create collection configs in src/collections/ with proper field definitions, a relationship field linking to Categories, an upload field for the featured image, and access control functions like create: ({ req }) => !!req.user on the Posts collection.
Common Pitfalls
-
Relationship field direction confusion. Claude creates bidirectional relationships manually. Payload relationship fields are unidirectional by default. Use the
hasMany: trueoption for one-to-many and Payload handles the join. -
Rich text editor configuration. Claude uses a basic text field for content. Payload’s rich text field supports Lexical editor with customizable toolbars, embedded blocks, and media — configure it with
editor: lexicalEditor({ features }). -
Deployment database mismatch. Claude develops with SQLite (default) and deploys to production. Payload supports PostgreSQL and MongoDB for production — configure the production adapter in
payload.config.tsand ensure schemas migrate properly.