Payload CMS Configuration: Required Parameters and Production Setup
Quick Answer
- Minimum viable config: You must provide
secret(a strong random string for encryption) anddb(a database adapter like@payloadcms/db-mongodbor@payloadcms/db-postgres). Without these two, Payload will not start. - First check: Ensure
payload.config.tsexists in your project root, or setPAYLOAD_CONFIG_PATHenvironment variable to point to it. Verify your database is running and accessible. - Production essentials: Set
serverURLto your absolute domain (protocol + domain, no paths), configure CORS and CSRF for security, and use cloud storage (S3/GCS) instead of local filesystem for media uploads. - Version boundary: Payload 3.x uses the configuration structure described here. If upgrading from Payload 2.x, note that
serverURLis no longer required but strongly recommended for email and admin panel features.
What Problem It Solves
Payload CMS solves the problem of building a custom, code-first content management system that gives developers full control over data models, API endpoints, and admin UI without sacrificing developer experience. Unlike UI-first CMS platforms, Payload lets you define everything in TypeScript configuration files, which means:
- Configuration is version-controlled and reviewable in pull requests
- Full type safety with auto-generated TypeScript types
- No lock-in to a specific UI paradigm
- Easy CI/CD integration
- Local API access without HTTP overhead
Minimal Working Configuration
Here's the smallest possible Payload configuration that will start a server:
TYPESCRIPT// payload.config.ts import { buildConfig } from 'payload' import { mongooseAdapter } from '@payloadcms/db-mongodb' import { slateEditor } from '@payloadcms/richtext-slate' export default buildConfig({ secret: process.env.PAYLOAD_SECRET || 'your-secret-key', db: mongooseAdapter({ url: process.env.DATABASE_URI || 'mongodb://127.0.0.1:27017/your-db', }), editor: slateEditor({}), collections: [], })
To run this, ensure you have the required packages installed:
BASHnpm install payload @payloadcms/db-mongodb @payloadcms/richtext-slate
Then start with:
BASHnpx payload
Parameters and Environment Variables
The buildConfig() function accepts a configuration object with the following key parameters:
| Parameter | Required | Description |
|---|---|---|
secret | Yes | Strong random string for encryption, password hashing, and JWT signing. Never commit to version control. |
db | Yes | Database adapter instance. Use @payloadcms/db-mongodb or @payloadcms/db-postgres. |
serverURL | No | Absolute URL of your app (e.g., https://example.com). No paths allowed. Required for email and admin panel features. |
collections | No | Array of Collection configurations for content types. |
globals | No | Array of Global configurations for singleton content. |
cors | No | Array of allowed origins for CORS. |
csrf | No | Array of URLs allowed to accept cookies from. |
admin | No | Admin panel configuration including custom components and live preview. |
editor | No | Rich text editor configuration. Defaults to Slate if not provided. |
localization | No | Enable multi-locale content with locale configuration. |
graphQL | No | GraphQL-specific configuration including custom queries and complexity limits. |
email | No | Email adapter configuration for transactional emails. |
upload | No | Base upload configuration for media handling. |
plugins | No | Array of Payload plugins. |
hooks | No | Array of root-level hooks. |
endpoints | No | Custom REST endpoints added to the Payload router. |
debug | No | Enable detailed error information in responses. |
telemetry | No | Set to false to disable anonymous telemetry. |
defaultDepth | No | Default relationship depth if user doesn't specify. |
maxDepth | No | Maximum allowed relationship depth (default: 10). |
cookiePrefix | No | Prefix for all cookies set by Payload. |
routes | No | Customize URL routes that Payload binds to. |
i18n | No | Internationalization configuration for admin UI languages. |
sharp | No | Pass the Sharp module for automatic image resizing and cropping. |
typescript | No | TypeScript generation settings. |
custom | No | Extension point for custom data (useful for plugins). |
Environment Variables
Common environment variables used with Payload:
BASHPAYLOAD_SECRET=your-strong-random-secret DATABASE_URI=mongodb://127.0.0.1:27017/your-database PAYLOAD_CONFIG_PATH=./src/payload.config.ts PAYLOAD_PUBLIC_SERVER_URL=https://example.com
Root Cause Analysis
Why secret and db are Required
Payload uses the secret for multiple security-critical operations:
- Password hashing: User passwords are salted and hashed using this secret
- JWT signing: Authentication tokens are signed with this secret
- Encryption: Any field-level encryption uses this secret as part of the key derivation
The db parameter is required because Payload is a database-driven CMS. Without a database adapter, there's no storage layer for content, users, or configuration. The adapter handles:
- Connection pooling and lifecycle
- Query translation from Payload's API to database-specific queries
- Migration management
- Transaction support (PostgreSQL adapter)
Why serverURL Matters Even Though It's Optional
While serverURL is technically optional, omitting it breaks several features:
- Email functionality: Password reset emails and other transactional emails need an absolute URL to generate links
- Admin panel: Some admin features like preview URLs and redirects depend on knowing the server URL
- CORS/CSRF: The server URL is used as a default allowed origin
Common Errors and Fixes
Error: Cannot find module 'payload'
Cause: Payload package not installed or not in node_modules.
Fix:
BASHnpm install payload # or yarn add payload # or pnpm add payload
If using pnpm, ensure your .npmrc has shamefully-hoist=true or use pnpm install --shamefully-hoist.
Error: Payload config not found
Cause: Payload cannot locate payload.config.ts in the project root.
Fix: Either move the file to the project root, or set the PAYLOAD_CONFIG_PATH environment variable:
BASHPAYLOAD_CONFIG_PATH=./src/config/payload.config.ts npx payload
Error: MongoNetworkError: connect ECONNREFUSED 127.0.0.1:27017
Cause: MongoDB is not running or the connection string is wrong.
Fix:
- Start MongoDB:
mongod(or use Docker:docker run -d -p 27017:27017 mongo) - Verify your
DATABASE_URIordbconfiguration URL is correct - For MongoDB Atlas, check IP whitelist and network access settings
Error: Forbidden - You are not allowed to perform this action.
Cause: Access control rules are blocking the request.
Fix: Check your collection or global access configuration. For public read access, ensure:
TYPESCRIPTaccess: { read: () => true, // Allow public read create: () => true, // Allow public create (if needed) }
Production Notes and Security Checks
Critical Production Requirements
-
Secret management: Never hardcode
secret. Use environment variables or a secrets manager:BASH# Generate a strong secret openssl rand -base64 32 -
Database: Use a managed database service (MongoDB Atlas, AWS RDS for PostgreSQL) with:
- SSL/TLS enabled
- IP whitelisting or VPC peering
- Automated backups
-
Media storage: Replace local filesystem with cloud storage:
TYPESCRIPTimport { s3Adapter } from '@payloadcms/plugin-cloud-storage/s3' export default buildConfig({ // ... plugins: [ cloudStorage({ collections: { media: { adapter: s3Adapter({ config: { region: process.env.S3_REGION, credentials: { accessKeyId: process.env.S3_ACCESS_KEY_ID, secretAccessKey: process.env.S3_SECRET_ACCESS_KEY, }, }, bucket: process.env.S3_BUCKET, }), }, }, }), ], }) -
CORS and CSRF: Restrict to your frontend domain:
TYPESCRIPTexport default buildConfig({ // ... cors: ['https://your-frontend.com'], csrf: ['https://your-frontend.com'], }) -
Depth limits: Set
maxDepthto a reasonable value (2-3) to prevent performance issues from deep relationship queries.
Concurrency and Data Integrity
Payload does not implement optimistic or pessimistic locking by default. In high-concurrency scenarios (multiple editors modifying the same document), enable the versions feature:
TYPESCRIPTconst Posts = { slug: 'posts', versions: { drafts: true, maxPerDoc: 10, }, // ... }
This creates a draft/version history that prevents data loss during concurrent edits.
FAQ
Q: How do I choose between MongoDB and PostgreSQL for Payload?
A: Choose MongoDB if you need flexible schemas, rapid iteration, and complex nested relationships. Choose PostgreSQL if you require strict data integrity, SQL-based reporting, or need to integrate with existing relational databases. Note that the PostgreSQL adapter is newer and some features (like advanced localization) may be less mature than the MongoDB adapter.
Q: Can I use Payload with Next.js?
A: Yes, Payload has first-class Next.js support. The recommended approach is to run Payload as an embedded API within your Next.js app using the @payloadcms/nextjs package. This gives you access to Payload's local API directly in your server components and route handlers without HTTP overhead.
Q: How do I handle file locking when scaling horizontally?
A: Never use the local filesystem for media uploads in production. Always use a cloud storage adapter (S3, GCS, Azure Blob) with the @payloadcms/plugin-cloud-storage plugin. This ensures all instances share the same storage backend and avoids file locking issues.