Exploring Payload CMS: A Code-First Headless CMS
What is Content Management System (CMS)
Content Management System (CMS) is software that allows users or groups of users (called “Authors”) to organize and manage content for websites. CMS supports frequent content updates to websites and other digital platforms.
Headless CMS
Traditional CMS platforms provide a user interface (UI) and comprehensive tools to manage and author content to generate web pages. Typical examples include WordPress and Drupal.
Headless CMS is a modern approach to content management. It focuses on providing content, most typically in the form of JSON, through REST APIs or GraphQL. This approach enables developers to design and build customized experiences across various digital touchpoints while using the same or similar content.
Payload CMS
Payload is a next-generation application framework that can be used as a Content Management System, enterprise tool framework, headless commerce platform, or digital asset management tool.
Before proceeding, we will examine various JavaScript Server-Side Rendering (SSR) frameworks used for website development. This exploration will help us understand the benefits of using PayloadCMS.
As per Google Developer website, there are many Javascript FE frameworks are evolving and converging on variety of features including SSR, component based development etc. Below chart helps understand better
PayloadCMS is a Next.js-native headless CMS that can be seamlessly integrated with any existing Next.js application or hosted as a separate application.
It is open-source under the MIT license and can be self-hosted on any platform that supports Node.js. You can also deploy it serverlessly to platforms like Vercel, even within your existing Next.js application.
Code-First Approach
In Payload, the entire schema for managing content is created using code and is version-controlled, much like a proper backend. This is a key reason why it’s used beyond a traditional CMS, serving as an ORM framework, storefront, and enterprise tooling framework. Once the schema is defined, non-technical users/authors can independently utilize the Admin Panel to manage content as needed, without any coding knowledge.
Beyond its out-of-the-box features, Payload is highly flexible, allowing users to customize it to their specific requirements, whether it’s the database, the admin UI, or any other aspect. Furthermore, it can be hosted on platforms like Vercel or any other serverless environment.
Lets See in Action
We will use a pseudo use case: a community site displaying upcoming events, pictures on the homepage, its speakers, etc. This is currently a static site, and the community owners now desire to manage content dynamically. The existing application is built with Next.js, and they prefer a Headless CMS solution.
A variety of headless CMS options are available in the industry, including Strapi (I’m a fan myself — you can watch my video here), Sanity, Contentful, and Payload CMS. We ultimately selected Payload CMS due to its code-first approach, flexibility, and the ability to coexist within the same Next.js application. Yes, no separate CMS deployments are required.
To begin implementing Payload CMS into your existing Next.js app, install the following, ensuring that your existing Next.js app is running on Next.js 15 and React 18.
npm i payload @payloadcms/next @payloadcms/richtext-lexical sharp graphql
Copy some files from Payload CMS blank template to the (payload) folder inside NextJs app folder. This contains the code for Admin panel and other Payload configs
Do not forget to copy the payload.config.ts and payload-types.ts files as well
Then select a choice of DB to store the content, it can be Mongo, Postgres or one from the list here. For this use case we will use Postgres, to install the adapter
pnpm i @payloadcms/db-postgres
Once done, configure the Postgres credentials, in payload.config.ts
import { postgresAdapter } from '@payloadcms/db-postgres'
// Rest of the Imports
export default buildConfig({
// Rest of the config
db: postgresAdapter({
pool: {
connectionString: process.env.DATABASE_URI || '',
},
}),
})
Update the next.config.js file
import { withPayload } from '@payloadcms/next/withPayload'
/** @type {import('next').NextConfig} */
const nextConfig = {
experimental: {
reactCompiler: false
}
};
export default withPayload(nextConfig)
Now its time to define the schema, but lets understand two key concepts before that. Collections and Globals
Collections — A Collection is a group of records, called Documents, that all share a common schema. You can define as many Collections as your application needs. In blog website list of pages can be collection
Globals are in many ways similar to Collections, except that they correspond to only a single Document. You can define as many Globals as your application needs. Content within a single page like HomePage, About us page are good examples.
To make this blog simple, for our use case we will choose Globals to manage the content for home page. Create homepage.ts file within globals folder under Nextjs app folder. We are trying to add dynamic menu, hero image and text on top of hero image in home page, which can be managed dynamically.
import type { GlobalConfig } from 'payload'
export const HomePage: GlobalConfig = {
slug: 'homepage',
access: {
read: () => true,
},
fields: [
{
name: 'menu',
type: 'array',
fields: [
{
name: 'menu1',
type: 'text',
}
],
maxRows: 6,
},
{
name: 'textonimage',
type: 'text',
},
{
name: 'heroimage',
type: 'upload',
relationTo: 'media',
filterOptions: {
mimeType: { contains: 'image' },
},
}
}
Add homepage globals to payload config
import { postgresAdapter } from '@payloadcms/db-postgres'
// Rest of the config goes here
import { HomePage } from './app/globals/homepage'
export default buildConfig({
// Rest of the config goes here
db: postgresAdapter({
pool: {
connectionString: process.env.DATABASE_URI || '',
},
}),
globals: [HomePage],
})
All set run the Nextjs app and navigate to payload cms admin panel
npm run dev
First time would be asked to create user, if not login to the admin panel
Homepage will be added as global and you can add the content required for your page by navigating to HomePage tile.
Next you need to access this data, Payload CMS provides 3 ways to access the content data.
Local API
- Do the same things as REST/GraphQL, but directly on your server.
- Super fast and efficient, especially for apps using Payload.
- Ideal for apps which bundles payload in it
REST API
- A simple way to interact with your data using commands like “get” and “create.”
- Includes helpful features like automatic pagination and sorting.
- Ideal for Apps integrating with Payload CMS hosted as separate app
GraphQL API
- A flexible way to get data, letting you ask for only what you need.
- Accessible by default at /api/graphql (but customizable).
For this use case since Payload CMS is bundled with NextJs we will be using Local API to query data. The query will happen using the slug, refer below code
// components
import { Navbar, Footer } from "@/components";
// sections
import Hero from "./hero";
import { getPayload } from 'payload'
import config from '@payload-config'
export default async function Portfolio() {
const payload = await getPayload({ config })
const result = await payload.findGlobal({
slug: 'homepage', // required
})
return (
<>
<Navbar menu={result.menu} />
<Hero imageUrl={result.heroimage.url} title={result.textonimage}/>
</>
);
}
Now let us run the app and see the result. Attached video below
Finishing thoughts
The industry offers a vast array of CMS options, catering to diverse use cases and enterprise needs, including both open-source and commercial solutions. For developers utilizing Next.js as their web development framework, Payload CMS is an excellent choice to explore.