# Payload CMS — Open Source Fullstack Next.js Headless CMS > Payload is the open-source headless CMS and app framework built on Next.js. TypeScript-first, code-configured, with instant admin panel, auth, and file uploads. ## Install Save the content below to `.claude/skills/` or append to your `CLAUDE.md`: ## Quick Use ```bash npx create-payload-app@latest my-project cd my-project npm run dev ``` Open `http://localhost:3000/admin` — create your first admin user and start building. ## Intro **Payload** is an open-source, fullstack Next.js framework that gives you an instant backend with a headless CMS, admin panel, authentication, file uploads, and more — all configured in TypeScript code. Unlike traditional CMS platforms that are GUI-configured, Payload uses a code-first approach where your content schema IS your source of truth. With 41.7K+ GitHub stars and MIT license, Payload has become one of the fastest-growing headless CMS solutions. Version 3.0 is built directly into Next.js, eliminating the traditional backend/frontend separation. ## What Payload Does Payload provides a complete backend toolkit: - **Headless CMS**: Define content types in TypeScript, get a full admin UI and API automatically - **Authentication**: Built-in auth with email/password, magic links, OAuth, and RBAC - **Admin Panel**: Auto-generated React admin interface with custom components support - **File Uploads**: Built-in media management with image resizing and cloud storage - **Access Control**: Field-level, collection-level, and document-level permissions - **Hooks**: Before/after hooks on all CRUD operations for custom business logic - **Localization**: Multi-language content with field-level translations - **Versions & Drafts**: Full version history with draft/publish workflow ## Architecture (v3 — Next.js Native) ``` ┌─────────────────────────────────────┐ │ Next.js App │ │ ┌─────────┐ ┌──────────────────┐ │ │ │ Your │ │ Payload Admin │ │ │ │ Frontend│ │ (/admin) │ │ │ │ Pages │ │ React UI │ │ │ └────┬────┘ └────────┬─────────┘ │ │ │ │ │ │ ┌────┴────────────────┴─────────┐ │ │ │ Payload Core Engine │ │ │ │ Collections, Globals, Auth │ │ │ │ Hooks, Access Control │ │ │ └───────────────┬───────────────┘ │ └──────────────────┼──────────────────┘ │ ┌──────┴───────┐ │ MongoDB / │ │ PostgreSQL │ └──────────────┘ ``` ## Getting Started ### Create a Project ```bash npx create-payload-app@latest my-app # Choose: Next.js template # Choose: MongoDB or PostgreSQL # Choose: Blank or with sample data ``` ### Define a Collection ```typescript // collections/Posts.ts import type { CollectionConfig } from 'payload'; export const Posts: CollectionConfig = { slug: 'posts', admin: { useAsTitle: 'title', }, access: { read: () => true, create: ({ req: { user } }) => Boolean(user), update: ({ req: { user } }) => Boolean(user), delete: ({ req: { user } }) => user?.role === 'admin', }, fields: [ { name: 'title', type: 'text', required: true, }, { name: 'content', type: 'richText', }, { name: 'author', type: 'relationship', relationTo: 'users', }, { name: 'status', type: 'select', options: ['draft', 'published'], defaultValue: 'draft', }, { name: 'publishedDate', type: 'date', }, { name: 'featuredImage', type: 'upload', relationTo: 'media', }, ], }; ``` ### Payload Config ```typescript // payload.config.ts import { buildConfig } from 'payload'; import { mongooseAdapter } from '@payloadcms/db-mongodb'; import { slateEditor } from '@payloadcms/richtext-slate'; import { Posts } from './collections/Posts'; import { Users } from './collections/Users'; import { Media } from './collections/Media'; export default buildConfig({ collections: [Posts, Users, Media], editor: slateEditor({}), db: mongooseAdapter({ url: process.env.DATABASE_URI || '', }), admin: { user: Users.slug, }, }); ``` ### Query Data in Your App ```tsx // app/posts/page.tsx import { getPayload } from 'payload'; import configPromise from '@payload-config'; export default async function PostsPage() { const payload = await getPayload({ config: configPromise }); const posts = await payload.find({ collection: 'posts', where: { status: { equals: 'published' } }, sort: '-publishedDate', limit: 10, }); return (