Payload CMS Integration
Connect Forest SEO to Payload CMS for headless content management with flexible schema mapping and API-based publishing.
Connect Forest SEO to Payload CMS for powerful headless content management. Payload's flexible architecture allows custom field mapping, making it perfect for developers who need control over their content structure.
✅ Prerequisites
Before connecting Payload CMS, ensure you have:
| Requirement | Details | Documentation |
|---|---|---|
| Payload CMS Instance | Self-hosted or cloud | payloadcms.com |
| API Access | REST or GraphQL enabled | API Docs |
| API Key | Authentication token | Authentication |
| Collections Configured | Content schema defined | Collections |
| Admin Access | User with API permissions | Access Control |
💡 Note
Payload CMS is fully open-source and self-hosted. You have complete control over your data and deployment.
🚀 Setup Guide
Step 1: Prepare Payload CMS
Step 2: Configure Collections
Example Blog Post Collection:
// collections/Posts.ts
import { CollectionConfig } from 'payload/types';
const Posts: CollectionConfig = {
slug: 'posts',
admin: {
useAsTitle: 'title',
},
access: {
read: () => true,
},
fields: [
{
name: 'title',
type: 'text',
required: true,
},
{
name: 'content',
type: 'richText',
required: true,
},
{
name: 'slug',
type: 'text',
required: true,
unique: true,
},
{
name: 'metaTitle',
type: 'text',
maxLength: 60,
},
{
name: 'metaDescription',
type: 'textarea',
maxLength: 160,
},
{
name: 'featuredImage',
type: 'upload',
relationTo: 'media',
},
{
name: 'categories',
type: 'relationship',
relationTo: 'categories',
hasMany: true,
},
{
name: 'tags',
type: 'relationship',
relationTo: 'tags',
hasMany: true,
},
{
name: 'status',
type: 'select',
options: [
{ label: 'Draft', value: 'draft' },
{ label: 'Published', value: 'published' },
],
defaultValue: 'draft',
},
],
};
export default Posts;
Step 3: Create API User
Generate API Credentials:
- Login to Payload Admin (usually at
/admin) - Navigate to Users collection
- Click "Create New"
- Fill in user details:
- Email: [email protected] (or your preference)
- Password: (secure password)
- Role: Admin or API access role
- Save user
Generate API Key:
# Via Payload API
POST /api/users/login
{
"email": "[email protected]",
"password": "your-secure-password"
}
# Response includes JWT token
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"user": { ... }
}
Save the JWT token for Forest SEO integration.
Step 4: Connect in Forest SEO
In Forest SEO Dashboard:
-
Go to Settings → Integrations
-
Click "Add Integration"
-
Select "Payload CMS"
-
Fill in connection details:
| Field | Value | Example |
|---|---|---|
| Name | Integration identifier | "My Payload Blog" |
| API URL | Payload API endpoint | https://cms.example.com/api |
| API Key | JWT token from Step 3 | eyJhbGciOiJIUzI1NiIs... |
| User Collection | Auth collection slug | users (default) |
| Default Collection | Content collection | posts |
- Click "Test Connection" to verify
- If successful, click "Save Integration"
⚙️ Field Mapping
Understanding Field Mapping
Payload CMS is schema-flexible, so Forest SEO needs to know how to map content fields:
Default Field Mappings
Configure how Forest SEO fields map to your Payload schema:
{
"default_fields": {
"title": "title",
"content": "content",
"slug": "slug",
"excerpt": "excerpt",
"meta_title": "metaTitle",
"meta_description": "metaDescription",
"featured_image": "featuredImage",
"status": "status",
"published_at": "publishedAt",
"categories": "categories",
"tags": "tags"
}
}
Field Configuration:
| Forest SEO Field | Payload Field | Type | Required |
|---|---|---|---|
| title | title | text | ✅ |
| content | content | richText | ✅ |
| slug | slug | text | ✅ |
| excerpt | excerpt | textarea | ⭕ |
| meta_title | metaTitle | text | ⭕ |
| meta_description | metaDescription | textarea | ⭕ |
| featured_image | featuredImage | upload | ⭕ |
| status | status | select | ⭕ |
| categories | categories | relationship | ⭕ |
| tags | tags | relationship | ⭕ |
Collection-Specific Mappings
For multiple content types, configure per-collection mappings:
{
"collection_mappings": {
"posts": {
"title": "title",
"content": "body",
"featured_image": "coverImage"
},
"pages": {
"title": "pageTitle",
"content": "pageContent",
"featured_image": "heroImage"
},
"products": {
"title": "productName",
"content": "description",
"featured_image": "productImage"
}
}
}
Use Case:
- Different collections have different schemas
- Map Forest SEO fields to appropriate Payload fields
- Maintain consistency across content types
📝 Content Type Support
Rich Text Handling
Payload supports multiple rich text editors:
Forest SEO Compatibility:
| Editor | Support | Format |
|---|---|---|
| Lexical | ✅ Full | JSON (native) |
| Slate | ✅ Full | JSON |
| Markdown | ✅ Full | Markdown |
| HTML | ✅ Full | HTML string |
Supported Payload Field Types
| Payload Type | Forest SEO Mapping | Notes |
|---|---|---|
| text | Text fields | Direct mapping |
| textarea | Long text | Meta descriptions, excerpts |
| richText | HTML/JSON content | Main article body |
| number | Numbers | Reading time, word count |
| date | Timestamps | Published date, updated date |
| select | Single choice | Status, category |
| relationship | Links to other collections | Categories, tags, authors |
| upload | Media references | Featured images, galleries |
| array | Lists | Multiple values |
| blocks | Flexible content | Advanced layouts |
🖼️ Media Management
Media Upload Configuration
{
"media": {
"collection": "media",
"folder_field": "folder",
"alt_field": "alt",
"caption_field": "caption",
"default_folder": "forest-seo-uploads"
}
}
Media Settings:
| Setting | Description | Default |
|---|---|---|
| collection | Media collection slug | media |
| folder_field | Folder organization field | folder |
| alt_field | Alt text field name | alt |
| caption_field | Caption field name | caption |
| default_folder | Upload destination | forest-seo-uploads |
Image Upload Process
Image Handling:
- Upload to Payload Media collection
- Store in configured folder
- Set alt text and caption
- Reference by ID in posts
- Support multiple sizes (Payload generates)
🔗 Relationships & Taxonomies
Categories Configuration
{
"taxonomy": {
"categories": {
"collection": "categories",
"label_field": "name",
"slug_field": "slug",
"auto_create": true
},
"tags": {
"collection": "tags",
"label_field": "name",
"slug_field": "slug",
"auto_create": true
}
}
}
Taxonomy Options:
| Field | Description | Example |
|---|---|---|
| collection | Taxonomy collection slug | categories, tags |
| label_field | Display name field | name, title |
| slug_field | URL-safe identifier | slug |
| auto_create | Create if doesn't exist | true, false |
Relationship Handling
Payload Relationships:
Payload uses relationship fields to link documents:
// In Post collection
{
name: 'categories',
type: 'relationship',
relationTo: 'categories',
hasMany: true, // Multiple categories
}
{
name: 'author',
type: 'relationship',
relationTo: 'users',
hasMany: false, // Single author
}
Forest SEO Behavior:
- Looks up existing relationships by slug/name
- Creates new entries if
auto_create: true - Links by ID in Payload
- Maintains referential integrity
🌍 Localization Support
Multi-Language Configuration
Payload supports localization out of the box:
// payload.config.ts
export default buildConfig({
localization: {
locales: ['en', 'es', 'fr', 'de'],
defaultLocale: 'en',
fallback: true,
},
});
Forest SEO Integration:
{
"locale_field": "locale",
"localized_fields": ["title", "content", "metaDescription"]
}
Multi-Language Flow:
✅ Testing Your Integration
Connection Test
Verify API connectivity:
Test Checklist:
- Click "Test Connection" in integration settings
- Verify:
- API URL is reachable
- Authentication succeeds
- Collections are accessible
- Write permissions granted
- Media upload works
- Review test results
- Fix any issues
- Save integration
Test Publish
Steps:
- In Forest SEO, select an article
- Click "Publish" → "Payload CMS"
- Choose collection (if multiple)
- Set status to
draft - Click "Publish Now"
- Wait for confirmation
- Check Payload Admin:
- Navigate to your collection
- Find the published document
- Verify all fields mapped correctly
- Check media uploaded
- Review relationships
🔧 Troubleshooting
Common Issues
Authentication Errors
Problem: "Unauthorized" or "Invalid token"
Solutions:
- Regenerate JWT token via Payload login
- Check token hasn't expired (Payload default: 7 days)
- Verify API user has correct permissions
- Ensure API URL is correct (include
/api)
Token Refresh:
POST /api/users/refresh-token
Authorization: Bearer <old-token>
# Returns new token
Field Mapping Errors
Problem: Fields not saving correctly
Solutions:
| Issue | Fix |
|---|---|
| Field not found | Check field name spelling |
| Type mismatch | Ensure field types compatible |
| Required field missing | Map all required fields |
| Rich text format | Set correct content type |
Debug Steps:
- Review Payload collection config
- Check field names match exactly
- Verify field types are supported
- Test with minimal field set first
Media Upload Issues
Problem: Images not uploading
Check:
- Media collection exists and is configured
- API user has upload permissions
- File size within Payload limits
- Correct media collection slug in mapping
- Folder field (if used) exists
Payload Media Permissions:
// collections/Media.ts
access: {
create: () => true, // Allow uploads
read: () => true,
update: () => true,
}
🚀 Advanced Features
Custom Blocks Support
Payload's block fields allow flexible content structures:
{
name: 'layout',
type: 'blocks',
blocks: [
{
slug: 'richText',
fields: [{ name: 'content', type: 'richText' }],
},
{
slug: 'callout',
fields: [
{ name: 'text', type: 'text' },
{ name: 'style', type: 'select', options: ['info', 'warning', 'success'] },
],
},
],
}
Forest SEO Integration:
- Converts HTML to appropriate blocks
- Maps semantic elements to block types
- Preserves rich formatting
Webhooks & Events
Payload supports webhooks for real-time updates:
// payload.config.ts
export default buildConfig({
hooks: {
afterChange: [
async ({ doc, req, operation }) => {
if (operation === 'create') {
// Notify external systems
await fetch('https://example.com/webhook', {
method: 'POST',
body: JSON.stringify(doc),
});
}
},
],
},
});
Use Cases:
- Notify frontend when content published
- Trigger builds on Vercel/Netlify
- Update search indices
- Sync to CDN
GraphQL Support
Payload auto-generates GraphQL API:
Query Example:
query GetPosts {
Posts(limit: 10, where: { status: { equals: "published" } }) {
docs {
id
title
content
featuredImage {
url
alt
}
}
}
}
Forest SEO:
- Currently uses REST API
- GraphQL support planned
- Can be extended via custom implementation
📊 Monitoring & Performance
API Rate Limits
Payload doesn't enforce strict rate limits by default, but consider:
Best Practices:
| Aspect | Recommendation | Reason |
|---|---|---|
| Batch Operations | Limit to 10-20 concurrent | Avoid overwhelming server |
| Image Uploads | Sequential, not parallel | Prevent memory issues |
| Large Content | Chunk if >1MB | Network stability |
| Retries | Exponential backoff | Handle transient errors |
Performance Optimization
Tips for Large-Scale Publishing:
-
Index frequently queried fields
{ name: 'slug', type: 'text', unique: true, index: true, // Faster lookups } -
Use pagination for queries
- Limit results to 100 per request
- Implement cursor-based pagination
-
Optimize media uploads
- Resize images before upload
- Use CDN for serving media
- Enable Payload's image optimization
-
Cache API responses
- Implement Redis cache layer
- Cache collection configurations
- Invalidate on updates
🔗 Integration with Features
With Content Generation
- Generate AI content
- Map to custom schemas
- Publish to any collection
With Schedules
- Automate content publishing
- Support multiple collections
- Scale to 100+ posts/month
With Custom Development
- Extend with custom fields
- Build unique content types
- Full API control