| name | payload |
| description | Use when working with Payload projects (payload.config.ts, collections, fields, hooks, access control, Payload API). Use when debugging validation errors, security issues, relationship queries, transactions, or hook behavior. |
Payload Application Development
Payload is a Next.js native CMS with TypeScript-first architecture, providing admin panel, database management, REST/GraphQL APIs, authentication, and file storage.
Quick Reference
Quick Start
npx create-payload-app@latest my-app
cd my-app
pnpm dev
Minimal Config
import { buildConfig } from "payload";
import { mongooseAdapter } from "@payloadcms/db-mongodb";
import { lexicalEditor } from "@payloadcms/richtext-lexical";
import path from "path";
import { fileURLToPath } from "url";
const filename = fileURLToPath(import.meta.url);
const dirname = path.dirname(filename);
export default buildConfig({
admin: {
user: "users",
importMap: {
baseDir: path.resolve(dirname),
},
},
collections: [Users, Media],
editor: lexicalEditor(),
secret: process.env.PAYLOAD_SECRET,
typescript: {
outputFile: path.resolve(dirname, "payload-types.ts"),
},
db: mongooseAdapter({
url: process.env.DATABASE_URL,
}),
});
Essential Patterns
Basic Collection
import type { CollectionConfig } from "payload";
export const Posts: CollectionConfig = {
slug: "posts",
admin: {
useAsTitle: "title",
defaultColumns: ["title", "author", "status", "createdAt"],
},
fields: [
{ name: "title", type: "text", required: true },
{ name: "slug", type: "text", unique: true, index: true },
{ name: "content", type: "richText" },
{ name: "author", type: "relationship", relationTo: "users" },
],
timestamps: true,
};
For more collection patterns (auth, upload, drafts, live preview), see COLLECTIONS.md.
Common Fields
{ name: 'title', type: 'text', required: true }
{ name: 'author', type: 'relationship', relationTo: 'users', required: true }
{ name: 'content', type: 'richText', required: true }
{ name: 'status', type: 'select', options: ['draft', 'published'], defaultValue: 'draft' }
{ name: 'image', type: 'upload', relationTo: 'media' }
For all field types (array, blocks, point, join, virtual, conditional, etc.), see FIELDS.md.
Hook Example
export const Posts: CollectionConfig = {
slug: "posts",
hooks: {
beforeChange: [
async ({ data, operation }) => {
if (operation === "create") {
data.slug = slugify(data.title);
}
return data;
},
],
},
fields: [{ name: "title", type: "text" }],
};
For all hook patterns, see HOOKS.md. For access control, see ACCESS-CONTROL.md.
Access Control with Type Safety
import type { Access } from "payload";
import type { User } from "@/payload-types";
export const adminOnly: Access = ({ req }) => {
const user = req.user as User;
return user?.roles?.includes("admin") || false;
};
export const ownPostsOnly: Access = ({ req }) => {
const user = req.user as User;
if (!user) return false;
if (user.roles?.includes("admin")) return true;
return {
author: { equals: user.id },
};
};
Query Example
const posts = await payload.find({
collection: "posts",
where: {
status: { equals: "published" },
"author.name": { contains: "john" },
},
depth: 2,
limit: 10,
sort: "-createdAt",
});
const post = await payload.findByID({
collection: "posts",
id: "123",
depth: 2,
});
const post = await payload.findByID({
collection: "posts",
id: "123",
depth: 0,
});
For all query operators and REST/GraphQL examples, see QUERIES.md.
Getting Payload Instance
import { getPayload } from 'payload'
import config from '@payload-config'
export async function GET() {
const payload = await getPayload({ config })
const posts = await payload.find({
collection: 'posts',
})
return Response.json(posts)
}
import { getPayload } from 'payload'
import config from '@payload-config'
export default async function Page() {
const payload = await getPayload({ config })
const { docs } = await payload.find({ collection: 'posts' })
return <div>{docs.map(post => <h1 key={post.id}>{post.title}</h1>)}</div>
}
Logger Usage
payload.logger.error("Something went wrong");
payload.logger.error({ msg: "Failed to process", err: error });
payload.logger.error("Failed to process", error);
payload.logger.error({ message: "Failed", error: error });
Security Pitfalls
1. Local API Access Control (CRITICAL)
By default, Local API operations bypass ALL access control, even when passing a user.
await payload.find({
collection: "posts",
user: someUser,
});
await payload.find({
collection: "posts",
user: someUser,
overrideAccess: false,
});
When to use each:
overrideAccess: true (default) - Server-side operations you trust (cron jobs, system tasks)
overrideAccess: false - When operating on behalf of a user (API routes, webhooks)
See QUERIES.md#access-control-in-local-api.
2. Transaction Failures in Hooks
Nested operations in hooks without req break transaction atomicity.
hooks: {
afterChange: [
async ({ doc, req }) => {
await req.payload.create({
collection: "audit-log",
data: { docId: doc.id },
});
},
];
}
hooks: {
afterChange: [
async ({ doc, req }) => {
await req.payload.create({
collection: "audit-log",
data: { docId: doc.id },
req,
});
},
];
}
See ADAPTERS.md#threading-req-through-operations.
3. Infinite Hook Loops
Hooks triggering operations that trigger the same hooks create infinite loops.
hooks: {
afterChange: [
async ({ doc, req }) => {
await req.payload.update({
collection: "posts",
id: doc.id,
data: { views: doc.views + 1 },
req,
});
},
];
}
hooks: {
afterChange: [
async ({ doc, req, context }) => {
if (context.skipHooks) return;
await req.payload.update({
collection: "posts",
id: doc.id,
data: { views: doc.views + 1 },
context: { skipHooks: true },
req,
});
},
];
}
See HOOKS.md#context.
Project Structure
src/
├── app/
│ ├── (frontend)/
│ │ └── page.tsx
│ └── (payload)/
│ └── admin/[[...segments]]/page.tsx
├── collections/
│ ├── Posts.ts
│ ├── Media.ts
│ └── Users.ts
├── globals/
│ └── Header.ts
├── components/
│ └── CustomField.tsx
├── hooks/
│ └── slugify.ts
└── payload.config.ts
Type Generation
export default buildConfig({
typescript: {
outputFile: path.resolve(dirname, "payload-types.ts"),
},
});
import type { Post, User } from "@/payload-types";
Reference Documentation
- FIELDS.md - All field types, validation, admin options
- FIELD-TYPE-GUARDS.md - Type guards for runtime field type checking and narrowing
- COLLECTIONS.md - Collection configs, auth, upload, drafts, live preview
- HOOKS.md - Collection hooks, field hooks, context patterns
- ACCESS-CONTROL.md - Collection, field, global access control, RBAC, multi-tenant
- ACCESS-CONTROL-ADVANCED.md - Context-aware, time-based, subscription-based access, factory functions, templates
- QUERIES.md - Query operators, Local/REST/GraphQL APIs
- ENDPOINTS.md - Custom API endpoints: authentication, helpers, request/response patterns
- ADAPTERS.md - Database, storage, email adapters, transactions
- ADVANCED.md - Authentication, jobs, endpoints, components, plugins, localization
- PLUGIN-DEVELOPMENT.md - Plugin architecture, monorepo structure, patterns, best practices
Resources