Payload CMS
Open-source headless CMS built with TypeScript and React. Define your schema in code, get a fully-typed API and admin panel automatically. Self-hostable with zero vendor lock-in — runs inside your Next.js app.
Why Payload CMS?
TypeScript project needing a code-first, self-hosted CMS
Running the CMS inside your Next.js app (Payload v3 is a Next.js plugin)
Avoiding vendor lock-in and monthly CMS fees
Signal Breakdown
What drives the Trust Score
Download Trend
Last 12 months
Tradeoffs & Caveats
Know before you commitNon-technical content team needing a polished editor — Contentful or Sanity have a better editorial UX
Fully hosted with zero ops — Payload requires a server/database
Need real-time collaborative editing — Sanity's Studio is best-in-class here
Pricing
Free tier & paid plans
Open source — self-hosted free forever
Payload Cloud from $19/mo
MIT license — no per-seat or API call fees ever
Alternative Tools
Other options worth considering
The leading headless CMS for enterprise content teams. API-first content infrastructure with a powerful content modeling system, webhooks, and SDKs for any frontend framework.
Often Used Together
Complementary tools that pair well with Payload CMS
Learning Resources
Docs, videos, tutorials, and courses
Get Started
Repository and installation options
View on GitHub
github.com/payloadcms/payload
npx create-payload-app@latestnpm install payload @payloadcms/richtext-lexicalQuick Start
Copy and adapt to get going fast
// Create a Payload v3 Next.js project
// npx create-payload-app@latest
// payload.config.ts
import { buildConfig } from 'payload';
import { postgresAdapter } from '@payloadcms/db-postgres';
import { lexicalEditor } from '@payloadcms/richtext-lexical';
export default buildConfig({
secret: process.env.PAYLOAD_SECRET!,
db: postgresAdapter({ pool: { connectionString: process.env.DATABASE_URI! } }),
editor: lexicalEditor({}),
collections: [
{
slug: 'pages',
fields: [
{ name: 'title', type: 'text', required: true },
{ name: 'slug', type: 'text', unique: true },
{ name: 'hero', type: 'group', fields: [
{ name: 'heading', type: 'text' },
{ name: 'image', type: 'upload', relationTo: 'media' },
]},
{ name: 'content', type: 'richText' },
],
},
],
});Code Examples
Common usage patterns
Fetch posts in Next.js App Router
Query Payload CMS directly in a Server Component
// app/blog/page.tsx
import { getPayload } from 'payload';
import config from '@payload-config';
export default async function BlogPage() {
const payload = await getPayload({ config });
const { docs: posts } = await payload.find({
collection: 'posts',
where: { _status: { equals: 'published' } },
sort: '-publishedAt',
limit: 10,
depth: 1, // populate author relationship
});
return (
<div>
{posts.map((post) => (
<article key={post.id}>
<h2>{post.title}</h2>
<p>By {typeof post.author === 'object' ? post.author.name : post.author}</p>
</article>
))}
</div>
);
}Custom collection hook
Run server-side logic before saving a document
// In your payload.config.ts collection definition
import { CollectionConfig } from 'payload';
export const Posts: CollectionConfig = {
slug: 'posts',
hooks: {
beforeChange: [
async ({ data, operation }) => {
if (operation === 'create') {
// Auto-generate slug from title
data.slug = data.title
.toLowerCase()
.replace(/[^a-z0-9]+/g, '-')
.replace(/^-|-$/g, '');
}
return data;
},
],
afterChange: [
async ({ doc }) => {
// Revalidate Next.js cache on publish
if (doc._status === 'published') {
await fetch(`/api/revalidate?path=/blog/${doc.slug}`, { method: 'POST' });
}
},
],
},
fields: [
{ name: 'title', type: 'text', required: true },
{ name: 'slug', type: 'text', unique: true, admin: { readOnly: true } },
{ name: 'content', type: 'richText' },
],
};Community Notes
Real experiences from developers who've used this tool