Skip to content
Learni
View all tutorials
CMS Headless

How to Configure Payload CMS Expertly in 2026

Lire en français

Introduction

Payload CMS is a modern headless CMS built on Node.js and TypeScript. It provides an automatically generated admin panel while allowing deep customization through code. This expert tutorial covers installation, advanced collection configuration, lifecycle hooks, granular access control, and Next.js integration. You'll learn how to structure a maintainable project, secure data, and optimize performance for a production environment. Each step includes complete, functional code you can copy directly.

Prerequisites

  • Node.js 20+ and TypeScript 5.4+
  • Solid knowledge of Next.js App Router
  • PostgreSQL or MongoDB database
  • Vercel account or VPS server for deployment
  • Familiarity with hooks and middleware concepts

Project Initialization

terminal
npx create-payload-app@latest my-cms --template blank-typescript
cd my-cms
npm install

This command generates a Payload project ready for TypeScript with the minimal structure. The blank-typescript template already includes essential dependencies like payload and types.

Main Configuration

payload.config.ts
import { buildConfig } from 'payload/config'
import { mongooseAdapter } from '@payloadcms/db-mongodb'
import { slateEditor } from '@payloadcms/richtext-slate'
import { Users } from './collections/Users'
import { Posts } from './collections/Posts'

export default buildConfig({
  serverURL: process.env.PAYLOAD_PUBLIC_SERVER_URL || 'http://localhost:3000',
  admin: { user: Users.slug },
  collections: [Users, Posts],
  editor: slateEditor({}),
  db: mongooseAdapter({ url: process.env.DATABASE_URI || '' }),
  typescript: { outputFile: './payload-types.ts' },
})

The payload.config.ts file centralizes all configuration. It declares collections, the rich text editor, and the database adapter. This structure enables full typing via the automatically generated payload-types.ts.

Advanced Posts Collection

collections/Posts.ts
import { CollectionConfig } from 'payload/types'
export const Posts: CollectionConfig = {
  slug: 'posts',
  admin: { useAsTitle: 'title' },
  access: { read: () => true, create: ({ req }) => !!req.user },
  fields: [
    { name: 'title', type: 'text', required: true },
    { name: 'content', type: 'richText', required: true },
    { name: 'author', type: 'relationship', relationTo: 'users', required: true },
    { name: 'publishedAt', type: 'date' }
  ],
  hooks: {
    beforeChange: [({ data, operation }) => {
      if (operation === 'create') data.publishedAt = new Date()
      return data
    }]
  }
}

This collection defines a complete model with relationships, access control, and a beforeChange hook. The hook automatically injects the publication date on creation.

Advanced afterChange Hook

collections/Posts.ts
hooks: {
  afterChange: [async ({ doc, operation, req }) => {
    if (operation === 'update' && doc.publishedAt) {
      await req.payload.create({
        collection: 'revisions',
        data: { post: doc.id, snapshot: doc }
      })
    }
    return doc
  }]
}

The afterChange hook automatically creates revisions on every update. It runs after saving and has access to the full payload instance.

Granular Access Control

access/isAdminOrSelf.ts
import { Access } from 'payload/config'
export const isAdminOrSelf: Access = ({ req: { user } }) => {
  if (!user) return false
  if (user.role === 'admin') return true
  return { id: { equals: user.id } }
}

This reusable access function checks the user's role or identity. It returns either true, false, or a MongoDB constraint to filter documents.

Best Practices

  • Always type hooks with Payload-generated types
  • Separate access rules into dedicated files for reusability
  • Use beforeValidate hooks for complex business validation
  • Version critical collections with a revisionNumber field
  • Configure CORS and security headers from development onward

Common Errors to Avoid

  • Forgetting to regenerate types after modifying collections
  • Using async hooks without awaiting payload operations
  • Defining conflicting access rules between admin and API
  • Neglecting media configuration and S3 storage in production

Going Further

Deepen your Payload knowledge with our Learni training dedicated to headless CMS and advanced TypeScript architecture.