Payload CMS
Payload is an open-source, TypeScript-first headless CMS and application framework built on Node.js and React, frequently integrated with Next.js for full-stack applications. It is currently at version 3.83.0 and maintains an active release cadence, with multiple minor versions and bug fixes published monthly, indicating continuous and rapid development. A key differentiator is its code-first configuration approach, allowing developers to define collections, globals, and other configurations programmatically using TypeScript. This facilitates strong version control, type safety, and a developer-centric workflow, distinguishing it from CMS platforms that primarily rely on UI-driven configuration. Payload also provides a robust GraphQL API out-of-the-box, flexible authentication mechanisms, and a customizable, self-hosted admin panel, making it suitable for both content management and complex application backends.
Common errors
-
TypeError: Cannot destructure property 'config' of 'ue(...)' as it is undefined.
cause This typically indicates a React context provider issue, often caused by inconsistent versions of `payload` or `@payloadcms/*` packages, or `react`/`react-dom` in your dependency tree, especially in monorepos.fixEnsure all `payload` and `@payloadcms/*` packages, along with `react`, `react-dom`, and `next`, are pinned to identical versions in your `package.json`. Remove `node_modules`, `package-lock.json`, `.payload`, and `.next` directories, then reinstall dependencies. -
ERROR (payload): Error: cannot connect to MongoDB. Details: URI does not have hostname, domain name and tld
cause Payload failed to establish a connection with the database. This can be due to an incorrect `MONGODB_URI` environment variable, network issues (firewall, IP whitelisting), incorrect credentials (e.g., special characters in passwords needing URI encoding), or the database server not running.fixVerify your `MONGODB_URI` in `.env` is correct and properly formatted. Ensure special characters in passwords are URI-encoded. Check network access, firewall rules, and if your database server is active and accessible. Consider using a tool like MongoDB Compass to test the connection independently. -
Unauthorized, you must be logged in to make this request
cause This error occurs when an authentication cookie is not being set or accepted correctly, or when access control functions deny the request.fixReview your Payload configuration for `CORS`, `CSRF`, and `cookie` settings. Explicitly whitelist your frontend domain in `CORS` rather than using `*`. Check browser developer tools for cookie rejection warnings to diagnose misconfigured cookie domain or security flags. Verify your access control functions are correctly implemented. -
CORS error when accessing with other ports, even with cors: '*'
cause Despite setting `cors: '*'` in `payload.config.ts`, browsers may still block requests due to preflight checks or specific browser security policies. This is often an issue when the serverURL is not correctly configured for the API.fixExplicitly list allowed origins in your `cors` array (e.g., `cors: ['http://localhost:3000', 'http://localhost:8080']`). Ensure your `serverURL` in `payload.config.ts` accurately reflects the URL where your Payload API is accessible. For specific HTTP methods, ensure they are correctly cased (e.g., 'PATCH' not 'patch').
Warnings
- breaking Migrating from Payload v2 to v3 involves significant architectural changes. The Admin Panel was replatformed from a React Router SPA to the Next.js App Router, deprecating `admin.bundler` property and bundler packages like `@payloadcms/bundler-webpack` and `@payloadcms/bundler-vite`. Custom components and views require updating import paths and interfaces.
- breaking Environment variables prefixed with `PAYLOAD_PUBLIC` are no longer available on the client-side in v3. This change aligns with Next.js's server-side rendering capabilities and impacts how client-side code accesses public environment variables.
- gotcha Dependency mismatches are a common source of cryptic runtime errors (e.g., 'Cannot destructure property 'config' of undefined') in Payload v3. All `payload`, `@payloadcms/*`, `react`, `react-dom`, and `next` packages must be installed at *exactly* the same version. Using caret (`^`) ranges can lead to inconsistencies.
- gotcha Payload builds (e.g., `npm run build`) can sometimes fail if an active database connection is not available at build time. This contradicts documentation and can be problematic in CI/CD environments where database access might be restricted during the build stage.
Install
-
npm install payload -
yarn add payload -
pnpm add payload
Imports
- payload
const payload = require('payload');import payload from 'payload';
- buildConfig
import buildConfig from 'payload/config'; import { buildConfig } from 'payload';import { buildConfig } from 'payload/config'; - CollectionConfig
import { CollectionConfig } from 'payload/config';import { CollectionConfig } from 'payload/types'; - User
import { User } from 'payload/dist/collections/Users/types'; // (example) import { User } from 'payload/types';import type { User } from 'payload/generated-types';
Quickstart
import { buildConfig } from 'payload/config';
import path from 'path';
const Users = {
slug: 'users',
auth: true,
admin: {
use=>: 'Email',
},
fields: [
{
name: 'name',
type: 'text',
},
],
};
export default buildConfig({
serverURL: process.env.PAYLOAD_PUBLIC_SERVER_URL ?? 'http://localhost:3000',
admin: {
user: 'users',
},
collections: [
Users,
{
slug: 'posts',
fields: [
{
name: 'title',
type: 'text',
required: true,
},
{
name: 'content',
type: 'richText',
},
{
name: 'author',
type: 'relationship',
relationTo: 'users',
},
],
},
],
typescript: {
outputFile: path.resolve(__dirname, 'payload-types.ts'),
},
graphQL: {
schemaOutputFile: path.resolve(__dirname, 'generated-schema.graphql'),
},
});
// To run this, you would typically have a server entry point like:
// import payload from 'payload';
// import express from 'express';
// import { connect } from 'mongoose'; // or '@payloadcms/db-postgres'
// const app = express();
// async function start() {
// await connect(process.env.MONGODB_URI ?? 'mongodb://localhost/payload');
// await payload.init({
// secret: process.env.PAYLOAD_SECRET ?? 'super-secret-default-key',
// express: app,
// onInit: () => {
// payload.logger.info(`Payload Admin URL: ${payload.getAdminURL()}`);
// },
// });
// app.listen(3000, async () => {
// payload.logger.info('Express is listening on port 3000');
// });
// }
// start();