Implementing custom SEO in Payload CMS

Introduction: Why SEO Matters
To reach your target audience, your website needs to stand out before users even visit it. Ranking on search engines like Google is often the first—and most critical—step in that process.
Even well-written content can remain invisible without proper optimization. This is where Search Engine Optimization (SEO) comes in.
SEO ensures that:
- Search engines can understand your content
- Your pages appear in relevant search results
- Users are encouraged to click through to your site
In this guide, you’ll learn how to implement SEO into your existing Payload CMS project. We’ll focus specifically on:
- Structuring SEO metadata
- Using the official Payload SEO plugin
- Connecting everything to your Next.js frontend
By the end, your site won’t just exist—it will be optimized to perform.
Step 1: Installing the Payload SEO Plugin
By default, Payload CMS stores content—but it does not automatically structure it for search engines.
To properly support SEO, we need fields such as:
- Meta Title
- Meta Description
- Social Sharing Image (Open Graph)
Install the plugin
npm install @payloadcms/plugin-seoRegister the plugin
Open your payload.config.ts file:
import { buildConfig } from 'payload'
import { seoPlugin } from '@payloadcms/plugin-seo'
export default buildConfig({
// your existing config
plugins: [
seoPlugin({
collections: ['pages'], // Adds SEO fields to this collection
uploadsCollection: 'media',
generateTitle: ({ doc }: any) => {
return doc?.title ? `${doc.title} | My Website` : 'My Website'
},
}),
],
})What this does
- Adds an SEO tab to your Pages collection
- Automatically creates fields for metadata
- Standardizes how SEO data is stored
Step 2: Structuring Your Pages Collection for SEO
Your Pages collection is where SEO data will live alongside your content.
If you don’t already have one, create or update it:
import { CollectionConfig } from 'payload'
export const Pages: CollectionConfig = {
slug: 'pages',
admin: {
useAsTitle: 'title',
},
versions: {
drafts: true,
maxPerDoc: 10,
},
fields: [
{
name: 'title',
type: 'text',
required: true,
},
{
name: 'slug',
type: 'text',
required: true,
unique: true,
index: true,
},
// SEO fields are automatically injected by the plugin
// Your content fields go here
],
}Why this matters
- Unique slugs ensure clean URLs (
/about,/contact) - Indexed fields improve query performance
- SEO data is stored per page, allowing full control
Step 3: Adding Global SEO Defaults
Not every page will have custom SEO data. You need fallbacks.
This is where Globals come in.
Create Site Settings
payload/globals/SiteSettings.ts
import { GlobalConfig } from 'payload'
export const SiteSettings: GlobalConfig = {
slug: 'site-settings',
fields: [
{
name: 'siteName',
type: 'text',
required: true,
defaultValue: 'My Website',
},
{
name: 'defaultDescription',
type: 'textarea',
label: 'Default SEO Description',
},
],
}Register it
import { SiteSettings } from './globals/SiteSettings'
export default buildConfig({
globals: [SiteSettings],
})Why this matters
- Prevents missing metadata
- Ensures consistent branding
- Provides fallback values for SEO
Step 4: Generating SEO Metadata in Next.js
Now we move to the frontend.
Next.js allows you to dynamically generate SEO metadata using the generateMetadata function.
Dynamic SEO setup
app/[slug]/page.tsx
import { getPayload } from 'payload'
import configPromise from '@payload-config'
import { Metadata } from 'next'
export async function generateMetadata({ params }): Promise<Metadata> {
const { slug } = params
const payload = await getPayload({ config: configPromise })
// Fetch page
const result = await payload.find({
collection: 'pages',
where: {
slug: { equals: slug },
},
})
const page = result.docs[0]
if (!page) return {}
return {
title: page.meta?.title || page.title,
description: page.meta?.description,
openGraph: {
title: page.meta?.title || page.title,
description: page.meta?.description,
images: page.meta?.image
? [{ url: (page.meta.image as any).url }]
: [],
},
}
}Step 5: Rendering the Page Content
After setting up SEO, render the page normally:
export default async function Page({ params }) {
const { slug } = params
const payload = await getPayload({ config: configPromise })
const result = await payload.find({
collection: 'pages',
where: {
slug: { equals: slug },
},
})
const page = result.docs[0]
if (!page) {
return <div>Page not found</div>
}
return (
<main>
<h1>{page.title}</h1>
{/* Render your content here */}
</main>
)
}Step 6: Best Practices for Enterprise SEO
To make your setup truly production-ready:
1. Always fill SEO fields
Encourage editors to:
- Write clear meta titles (50–60 characters)
- Write compelling descriptions (150–160 characters)
2. Use meaningful slugs
Good:
/seo-guide-payload
Bad:
/page-123
3. Add Open Graph images
These improve how your pages appear when shared on social platforms.
4. Use fallback values
Never leave metadata empty—use globals.
Conclusion
You’ve now implemented a complete SEO system in Payload CMS and Next.js.
What you achieved
- Structured SEO data in your CMS
- Automated metadata generation
- Search-engine-friendly frontend rendering
- Scalable architecture for future growth
Next Steps
To go even further, consider:
- Adding JSON-LD structured data
- Implementing sitemaps and robots.txt
- Using incremental static regeneration (ISR)
- Auditing with tools like Google Search Console
With this setup, your website is no longer just functional—it’s discoverable, competitive, and built for growth.
Enjoyed this article?
Discussion
No comments yet. Start the conversation!