
PayloadCMS Tips and Tricks

PayloadCMS is an open-source headless CMS that installs directly into your Next.js app. Based on your database schema, it automatically generates a powerful Admin Panel for managing CRUD operations.
It also provides fully type-safe ORM support for MongoDB, Postgres, and SQLite which makes it extremely developer-friendly.
After running PayloadCMS in production for over 2 years, here are some practical lessons and mistakes to avoid.
Don’t Update the Same Collection Inside afterRead or beforeRead
Payload provides lifecycle hooks that allow you to run logic around database operations:
- beforeChange
- afterChange
- beforeRead
- afterRead
These are powerful but easy to misuse. If you call payload.update() on the same collection inside a beforeRead or afterRead hook, you’ll create an infinite loop.
Why?
1// payload.update → database update → payload.read → database read
Calling update triggers another read.
That read triggers your hook again.
Which triggers another update.
And the cycle continues.
How to Prevent It
Use the context property to conditionally control when the update runs.
1// Example23import { CollectionAfterReadHook } from 'payload'4import axios from 'axios'5import { Server } from '@/payload-types'67export const populateServerDetails: CollectionAfterReadHook<Server> = async ({8 doc,9 context,10 req,11}) => {12 const { payload } = req1314 if (context.triggerUpdate) {15 const { data: remoteData } = await axios.get('https://remote.com')1617 await payload.update({18 collection: 'servers',19 data: {20 ...doc,21 ...remoteData,22 },23 context: {24 triggerUpdate: false, // prevent recursive execution25 },26 })27 }28}
Aviod JSON.stringify().parse() When Extending Config in Plugins
Payload plugins are a powerful way to extend and modify your base configuration. They receive the incoming config and return a modified version of it.
A common mistake is trying to deep-clone a collection using:
1JSON.parse(JSON.stringify(collection))
This looks harmless but it strips out functions. Hooks in Payload are async functions.
When you stringify and parse, those functions are removed. That means your hooks silently disappear.
Example of what not to do:
1import type { CollectionConfig, Config, Plugin } from 'payload'23export const yourPlugin =4 (): Plugin =>5 async (incomingConfig: Config): Promise<Config> => {6 const updatedCollections: CollectionConfig[] = (7 incomingConfig.collections || []8 ).map((collection) => {9 if (collection.slug === 'users') {10 // ❌ This removes hooks11 const shallowCopy = JSON.parse(JSON.stringify(collection))1213 return {14 ...shallowCopy,15 // extend config here16 }17 }1819 return collection20 })2122 return {23 ...incomingConfig,24 collections: updatedCollections,25 }26 }
Instead, carefully spread and extend only the parts you need.
Don’t Mix JSX and Payload Config in the Same Export
If you're using blocks with the Lexical editor, your folder structure might look like this:
1📁 collections2 └──📁 blog3 ├── config.ts4 └──📁 blocks5 └──📁 iframe6 ├── Component.tsx7 ├── config.ts8 └── index.ts
You might be tempted to export both config and JSX from a single file:
1// index.ts2import IframeComponent from './Component'3import iframeConfig from './config'45export { iframeConfig, IframeComponent }
And then import only the config in your collection:
1// collections/blog/config.ts23import { iframeConfig } from './blocks/iframe'
This can cause CSS or bundling errors in Payload v3. From Payload v3 onward, JSX files should not be imported inside the Payload config layer. Even if you're not directly importing the component, the shared export pulls it into the config bundle.
How to Prevent it
Separate config and JSX exports completely.
- Config files should only export configuration.
- Components should be imported only in frontend rendering logic.
Keep the boundaries clean.
Use MongoDB for Local Development
Postgres and SQLite require migrations when your schema changes. During heavy development, that adds friction. MongoDB, being schema-flexible, usually doesn’t require migrations for most schema updates.
If you're rapidly iterating on collections and fields, MongoDB can significantly speed up local development.
Join the PayloadCMS Community
This one is underrated. Payload community is Active, Helpful and Full of real-world examples.
- Release updates
- Showcases
- Solutions to edge-case problems
