dFlow Logo
PayloadCMS Tips and Tricks cover picture

PayloadCMS Tips and Tricks

Avatar
Pavan Bhaskar
12 Feb, 2026
PayloadCMSNextjs

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// Example
2
3import { CollectionAfterReadHook } from 'payload'
4import axios from 'axios'
5import { Server } from '@/payload-types'
6
7export const populateServerDetails: CollectionAfterReadHook<Server> = async ({
8 doc,
9 context,
10 req,
11}) => {
12 const { payload } = req
13
14 if (context.triggerUpdate) {
15 const { data: remoteData } = await axios.get('https://remote.com')
16
17 await payload.update({
18 collection: 'servers',
19 data: {
20 ...doc,
21 ...remoteData,
22 },
23 context: {
24 triggerUpdate: false, // prevent recursive execution
25 },
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'
2
3export 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 hooks
11 const shallowCopy = JSON.parse(JSON.stringify(collection))
12
13 return {
14 ...shallowCopy,
15 // extend config here
16 }
17 }
18
19 return collection
20 })
21
22 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📁 collections
2 └──📁 blog
3 ├── config.ts
4 └──📁 blocks
5 └──📁 iframe
6 ├── Component.tsx
7 ├── config.ts
8 └── index.ts

You might be tempted to export both config and JSX from a single file:

1// index.ts
2import IframeComponent from './Component'
3import iframeConfig from './config'
4
5export { iframeConfig, IframeComponent }

And then import only the config in your collection:

1// collections/blog/config.ts
2
3import { 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

Useful Resources