Payload is an open-source headless CMS designed for the Next.js/React stack. Unlike platforms where the schema is configured almost entirely via a graphical interface, in Payload, collections, fields, permissions, and business logic are defined in TypeScript, within the same application repository. It is not just a simple CMS: Payload presents itself as a backend for the modern web featuring content management, authentication, uploads, APIs, a job queue, and an extensible admin panel written in React. Being open-source and self-hosted, your data remains in your Postgres database, without being locked into a closed SaaS.
For those developing in TypeScript on Next.js, the main advantage is reducing the gap between a “CMS for editors” and an application with complex rules. There is no need to adapt a generic product with fragile plugins: models and behavior live in the code, versioned with Git, testable, and strongly typed.
In this article, our Front-end developer, Gianluca La Rosa, talks about Payload CMS through a project he realized, in which he was able to test the features of this CMS to build a website and a management application that share the same source of truth.
One schema, multiple applications
In many projects, the CMS only serves the showcase website. In reality, Payload can become the shared data layer for multiple Next.js apps talking to the same database. In the project from which I extracted these examples, the schema lives in a shared package imported by three distinct applications. The public site consumes the content; the Payload admin runs alongside the frontend; an internal app on a dedicated subdomain handles operations that must not end up in the association’s management panel.
All apps obtain the same Payload instance with just a few lines:
import configPromise from '@payload-config'
import { getPayload } from 'payload'
export async function getPayloadClient() {
return getPayload({ config: configPromise })
}
No custom REST APIs to maintain in parallel: Server Components and Server Actions directly call payload.find(), payload.update(), and so on. A single schema, a single set of migrations, and shared generated types.
Schema in code, not in a panel
Collections are defined as TypeScript objects: slugs, fields, hooks, permissions, and admin labels. After every modification, Payload generates a payload-types.ts file aligned with the database, providing autocomplete and type-checking across the entire project.
For PostgreSQL, it is best to deactivate the automatic “schema push” and rely on versioned migrations:
db: vercelPostgresAdapter({
pool: { connectionString: process.env.POSTGRES_URL },
push: false, // the schema evolves only with payload migrate
}),
On a project that lives for months or years, this is important: every change goes through a reviewable SQL migration, applied in CI and production using the exact same command. You do not rely on an implicit sync that “fixes everything” in dev but brings surprises in production.
Flexible permissions, beyond admin and editor
Many CMS platforms offer only a few fixed roles. Payload exposes access functions on every CRUD operation, with two response modes.
A binary permission, meaning true or false, across the entire collection:
export const canCreatePosts: Access = ({ req: { user } }) => {
return user?.roles?.includes('editor') ?? false
}
Or a query constraint: yes, but only on certain documents. The function returns a where filter, just like in a query:
export const canReadPosts: Access = ({ req: { user } }) => {
if (user?.roles?.includes('admin')) return true
// Editors only see their own articles
return { author: { equals: user?.id } }
}
Payload does not limit itself to filtering data in memory via Node.js; instead, it translates access constraints directly into SQL WHERE clauses before querying the database. This guarantees maximum performance by leveraging native indexes and eliminates security vulnerabilities at the root: if a user does not have permissions to see a specific record, for the database, that document simply does not exist.
In the reference project, this pattern is pushed even further: “specialist” roles for different editorial scopes (events, game catalogs, …), differentiated sidebar visibility, and media file permissions tied to a specific scope. By isolating the areas of competence for each user, this made management significantly clearer.
Hooks: behavior close to the data
Hooks (beforeChange, afterChange, afterRead, …) are where Payload sets itself apart from more content-only CMS platforms. You can execute logic every time a document is read, created, or modified, whether via admin, REST, or the Local API.
Two typical use cases, both present in the project:
- Derived fields: values calculated from other fields and the current date, automatically updated instead of being edited manually.
- Side effects: invalidating the Next.js cache when content is published, synchronizing linked records, or sending notifications.
A simplified revalidation hook:
export const revalidatePost: CollectionAfterChangeHook<Post> = ({
doc,
req: { context }
}) => {
if (context.disableRevalidate) return doc
if (doc._status === 'published') {
revalidatePath(`/articoli/${doc.slug}`)
revalidateTag('posts-sitemap')
return doc
}
}
In one specific case, instead of filling out a collection’s form to add an element, the user inserts a URL, which triggers a fetch that populates all the fields, saving a massive amount of time.
Extensible Admin
Being written entirely in React, the admin panel allows you to replace or complement any default element (fields, entire views, sidebar links) by specifying your own component in the configuration file.
For example, it is possible to define a ui type field that leverages built-in hooks like useAllFormFields to read form data and display a calculated summary instantly as the operator types:
{
name: 'paymentPreview',
type: 'ui',
admin: {
components: {
Field: '@/components/admin/PaymentPreview#PaymentPreview',
},
},
}
This approach allows for the creation of dynamic, custom interfaces without resorting to iframes or external plugins. Furthermore, the native Live Preview feature places the frontend preview right next to the editor, allowing you to test various mobile, tablet, and desktop breakpoints during editing.
Built-in multilingual support in the CMS
Payload handles translations at the field level (localized: true), complete with a default locale and fallback. Supported languages are declared in the config:
localization: {
locales: [
{ code: 'it', label: 'Italiano' },
{ code: 'en', label: 'English' },
],
defaultLocale: 'it',
fallback: true,
},
Defining translations within the CMS guarantees data model integrity, as the language exists in the schema before the website even displays it. Adding a language requires a controlled migration on Postgres, avoiding the typical misalignment issues found in static JSON files. In this way, the frontend only handles interface translations (buttons, fixed labels) and requests dynamic content from Payload that is already filtered at the source for the correct language.
Local API: server-side queries without HTTP
The public website does not fetch content via REST from the browser. Server Components query Payload server-side:
const { docs } = await payload.find({
collection: 'events',
where: { _status: { equals: 'published' } },
overrideAccess: false,
locale: 'it',
sort: '-date',
})
By utilizing the Local API within Server Components, Payload communicates directly via Node.js code, eliminating HTTP calls and the exposure of secret keys or tokens in the browser. To avoid metadata overhead or the exposure of sensitive hidden data within raw documents, the best practice involves mapping the results into lean DTOs, passing only the properties strictly necessary for rendering to the Client Components.
Plugins and infrastructure
Official plugins integrate directly into the configuration file, natively extending collections, types, and migrations. Thanks to the integrated Sharp library, image resizing and compression happen on the server during upload. Finally, the native job queue and cron system allow you to handle heavy background tasks using the same application database, without having to configure complex infrastructures.
It isn’t the WordPress ecosystem, but it covers typical needs; everything else can be extended with hooks and components, using the same language as the rest of the project.
Pros and Cons
Why Payload
- End-to-end TypeScript: schema, types, frontend, admin.
- Total control: self-hosted, open-source, no vendor lock-in on data.
- Native Next.js: admin, API, and site within the same monorepo.
- Extensibility: hooks, access, and admin UI without artificial limits.
- Performance: direct DB queries, no mandatory GraphQL.
Limitations to consider
- Initial learning curve: requires TypeScript/React expertise; it is not “install and forget”, and it is less immediate than WordPress for newcomers.
- Admin for occasional users: rarely ideal out-of-the-box.
- Ecosystem: fewer themes and third-party plugins.
- Operations: database, migrations, and storage are managed by you (unlike Contentful or Sanity).
- Rapid evolution: major upgrades must be planned.
Conclusion
Payload is not the tool to choose for a simple blog ready in five minutes. Instead, it becomes the ideal solution when the CMS needs to integrate deeply with the application, acting as a truly flexible backend. It is perfect when you need to share the same schema across multiple apps, manage granular permissions, embed business logic directly into hooks, or customize the admin interface without rebuilding a backoffice from scratch.
In real and complex contexts—such as an ecosystem combining a multilingual site, structured catalogs, restricted areas, and user management—Payload ensures that the data model and behaviors always remain consistent between the public site, the control panel, and internal applications.
There is certainly a higher initial investment in terms of development and configuration compared to a traditional CMS or a ready-to-use SaaS platform. However, this effort is amply rewarded over time: you get a robust, clean, and scalable architecture where a single, solid source of truth exists for all project data
Learn more about our front-end engineering services and contact us to meet our experts
Main Author: Gianluca La Rosa, Front-end developer @ Bitrock