Quick answer: Payload is an open-source, TypeScript-first headless CMS and application framework that installs inside your Next.js app rather than running next to it. Your data model lives in a single payload.config.ts file, Payload generates the admin panel and the TypeScript types from that file, and you query your content through three APIs — the Local API (direct database access in React Server Components), REST at /api, and GraphQL. Current stable is the 3.x line (3.90.2 on npm at the time of writing), requiring Node.js 20.18.1+ and Next.js 15.2.x–15.4.x or 16.2.6+.

First article in our Payload CMS series. Next: install Payload and build your first collection.

What Payload is, and what it is not

Most headless CMSs are separate servers you talk to over HTTP. You run Strapi or Directus in a container, model content in their admin UI, and your frontend fetches JSON. Payload inverts that: it is a library you install into your own Next.js application. There is no separate CMS process to host, no second codebase, and no REST round-trip when your own server needs data.

That has three practical consequences:

  • Nothing is fetched over the network locally. A React Server Component calls payload.find(...) and reads the database directly.
  • The schema is code, not clicks. Collections live in version control, review in pull requests, and deploy like any other source file.
  • Types come from the schema. Change a field and the generated TypeScript types change with it, so a frontend that reads a renamed field fails at compile time.

What it is not: it is not a hosted SaaS (you own the database and the deployment), not a page builder (the admin edits structured content, your React components own the design), and not a plugin-heavy ecosystem play — Payload’s philosophy is that you extend it by writing code in the config.

The six concepts that matter

Everything in Payload reduces to six ideas. Learn these and the documentation becomes obvious.

ConceptWhat it is
ConfigA deeply typed object in payload.config.ts that drives everything
DatabasePostgres, MongoDB or SQLite, plugged in by a database adapter
CollectionsGroups of documents that share a schema — your content types
GlobalsSame idea, for content there is only one of (footer, navigation, settings)
FieldsThe building blocks of a collection; they define the schema and generate the admin UI
Access ControlFunctions deciding who can read, create, update or delete each document

Two more you will meet immediately: Hooks, which run your code at points in a document’s lifecycle (beforeChange, afterChange, afterRead), and Authentication, a portable user system that works both for the admin panel and your own apps.

// payload.config.ts — the whole CMS in one object
import { buildConfig } from 'payload'
import { lexicalEditor } from '@payloadcms/richtext-lexical'
import { postgresAdapter } from '@payloadcms/db-postgres'
import { Posts } from './collections/Posts'
import { Users } from './collections/Users'

export default buildConfig({
  editor: lexicalEditor(),
  collections: [Users, Posts],
  secret: process.env.PAYLOAD_SECRET || '',
  db: postgresAdapter({
    pool: { connectionString: process.env.DATABASE_URL },
  }),
  typescript: { outputFile: 'payload-types.ts' },
})

That is the entire data layer. Adding a field is a code change, not an admin-UI configuration step that production cannot reproduce.

Fields define both the schema and the UI

A field is an object with at least a type. The same declaration creates the database column, the validation, the API shape, and the form control the editor sees.

Payload ships Data fields — text, textarea, number, checkbox, date, email, select, radio, code, json, point, relationship, upload, richText, plus the nested containers array, blocks, group and named tabs — and Presentational fields that organise the admin UI without storing anything (row, collapsible, unnamed tabs, ui). A newer virtual option lets a field appear in API responses without being persisted, which is how you expose a computed summary or a relationship’s field (for example virtual: 'author.name') without duplicating data.

import type { CollectionConfig } from 'payload'

export const Posts: CollectionConfig = {
  slug: 'posts',
  admin: { useAsTitle: 'title' },
  access: {
    read: ({ req: { user } }) => (user ? true : { status: { equals: 'published' } }),
    create: ({ req: { user } }) => Boolean(user),
    delete: ({ req: { user } }) => user?.role === 'admin',
  },
  fields: [
    { name: 'title', type: 'text', required: true },
    { name: 'slug', type: 'text', unique: true, index: true },
    { name: 'content', type: 'richText' },
    { name: 'status', type: 'select', defaultValue: 'draft',
      options: ['draft', 'published'] },
    { name: 'author', type: 'relationship', relationTo: 'users' },
    { name: 'tags', type: 'array', fields: [{ name: 'label', type: 'text' }] },
  ],
}

Notice the read rule: access control may return true, false, or a query constraint. Returning { status: { equals: 'published' } } means signed-out visitors can only ever see published posts — enforced in the database query, not in a template you might forget.

Three APIs, one query language

APIWhere it runsTypical use
LocalYour Node/React serverRSC pages, seeds, cron jobs, custom route handlers
RESTAny client over HTTP, at /apiMobile apps, static builds, third parties
GraphQL/api/graphql (+ playground)Typed clients, Apollo, graphql-request

All three share the same query language, so what you learn in one transfers:

// Local API inside a React Server Component — no HTTP, no latency
import { getPayload } from 'payload'
import config from '@payload-config'

export default async function BlogIndex() {
  const payload = await getPayload({ config })
  const { docs } = await payload.find({
    collection: 'posts',
    where: { status: { equals: 'published' } },
    sort: '-createdAt',
    limit: 10,
    depth: 1,
  })
  return <ul>{docs.map((p) => <li key={p.id}>{p.title}</li>)}</ul>
}
# The same query over REST
curl "https://example.com/api/posts?where[status][equals]=published&sort=-createdAt&limit=10&depth=1"

One caveat worth internalising early: the Local API skips access control by default (it assumes trusted server code) and hands you every document. Pass overrideAccess: false — and a user — when the operation runs on behalf of a visitor.

The package layout

Payload 3 split the monolith into focusable packages, all released in lockstep versions:

  • payload — core logic: operations, hooks, validation, access control, and all TypeScript types.
  • @payloadcms/next — the admin panel and the REST/GraphQL HTTP layer as Next.js routes.
  • @payloadcms/ui — the React component library behind the admin panel, reusable in your own extensions.
  • Database adapters — @payloadcms/db-postgres, @payloadcms/db-mongodb, @payloadcms/db-sqlite, @payloadcms/db-vercel-postgres.
  • Rich text — @payloadcms/richtext-lexical (recommended for new projects) or richtext-slate.

Because payload itself is framework-light, you can also run it headlessly outside Next.js when you need a custom server.

Why teams choose it — and when they should not

Choose Payload when: your team already writes TypeScript; you want content types reviewed like code; you need per-document access rules that are testable rather than clicked; you want one deployment instead of an app plus a CMS; or you are building an application (orders, memberships, dashboards) where the CMS is one part, not the whole product.

Look elsewhere when: your team is not JavaScript-based; you cannot host Node and a database yourself; you want a fully hosted SaaS with no infrastructure; non-technical editors need drag-and-drop page composition without developer involvement; or your content is mostly analytics-style reporting, where a BI tool fits better.

Version landscape in 2026

payload@latest on npm is the 3.x line (3.90.2 when this was written). Requirements are stricter than they look: Node.js 20.18.1+, and Next.js in one of the supported ranges — 15.2.9–15.2.x, 15.3.9–15.3.x, 15.4.11–15.4.x, or 16.2.6+. “Any recent Next.js” is not good enough; unsupported minor versions are a common source of confusing admin-panel errors.

A v4 canary line also exists, reported to tighten access-control defaults (so custom code must opt into skipping checks), default versions on, and require Next.js 16.2.6+. Treat that as a preview: check the official migration guide and the npm tags before starting a new project on it.

Key takeaways and challenge

  • Payload is a library inside your Next.js app, not a separate CMS server — no HTTP hop for your own data.
  • One payload.config.ts produces the database schema, the admin panel, and the TypeScript types.
  • Collections and globals model your content; fields are both schema and UI.
  • Access control returns true, false, or a query constraint — the query form is the one that scales safely.
  • Local API, REST and GraphQL share one query language; remember the Local API skips access checks unless you pass overrideAccess: false.
  • Check the supported Node and Next.js versions before you install; the canary v4 line is not latest yet.

Challenge: before writing any code, sketch your content model on paper — list the collections, mark which are auth-enabled, write the one-line access rule for read/create/update/delete on each, and decide for every field whether it is data, presentational, or virtual. Teams that skip this step end up re-modelling in production, which is far more expensive than an hour with a notebook.

Want a second pair of eyes on a Payload architecture? Ampersand Academy runs one-to-one mentoring on TypeScript and Next.js backends.

Is Payload CMS free to use?

Yes. Payload is MIT-licensed open source, so there is no licence fee. You pay for your own hosting and database, and the config, admin panel and APIs all run on infrastructure you control.

Does Payload CMS require Next.js?

Payload 3 installs into a Next.js application and documents support for specific ranges such as 15.2.x, 15.3.x, 15.4.x and 16.2.6 and newer. The core payload package can also run outside Next.js when you need a custom server.

Which database should I use with Payload?

Postgres if you want relational queries and plain SQL later, MongoDB if your documents are deeply nested, and SQLite for local prototypes. You choose one database adapter per project.

Is Payload a headless CMS or an application framework?

It is both. Collections, globals and the admin panel make it a headless CMS, while built-in authentication, access control, hooks and the Local API let you build complete applications around the same content.

What is the difference between the Local API and the REST API?

The Local API calls the database directly from your Node or React server code with no HTTP overhead, and it skips access control unless you pass overrideAccess as false. The REST API serves clients at /api and always enforces access control.

Last updated on · Written by