Payload is a modern, TypeScript-first headless CMS built on Next.js. Since Payload 2.0+ can run embedded in your Next.js app, analytics integration is seamless with your frontend.
Available Integrations
Analytics Platforms
- Next.js native integration
- Collection-based content tracking
- Admin panel analytics exclusion
- Server-side tracking via API routes
Tag Management
- Next.js GTM installation
- Data layer structure for Payload collections
- Rich text field tracking
- Block-based content analytics
Marketing Pixels
- Next.js integration methods
- Conversions API via hooks
- E-commerce tracking with Payload Commerce
- Privacy and consent management
Payload-Specific Integration Considerations
Next.js Native Architecture
Payload 2.0+ runs within your Next.js application:
- Embedded CMS: Payload runs in the same Next.js app
- Shared Auth: Same authentication context
- API Routes: Payload exposes REST and GraphQL at /api
- Admin UI: React-based admin at /admin
Collection-Based Tracking
Track content from Payload collections:
// In your Next.js page
import { getPayloadClient } from '../payload';
export default async function ArticlePage({ params }) {
const payload = await getPayloadClient();
const article = await payload.findByID({
collection: 'articles',
id: params.id,
depth: 2
});
// Track article view
return (
<>
<Script
id="article-tracking"
strategy="afterInteractive"
dangerouslySetInnerHTML={{
__html: `
gtag('event', 'article_view', {
article_id: '${article.id}',
article_title: '${article.title}',
author: '${article.author?.name || 'Unknown'}',
collection: 'articles'
});
`
}}
/>
<ArticleContent article={article} />
</>
);
}
Admin Panel Exclusion
Exclude Payload admin routes from analytics:
// In your analytics component
import { usePathname } from 'next/navigation';
export function Analytics() {
const pathname = usePathname();
// Skip analytics for admin routes
if (pathname.startsWith('/admin')) {
return null;
}
return (
<Script
src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXX"
strategy="afterInteractive"
/>
);
}
Hooks for Server-Side Analytics
Use Payload hooks for server-side event tracking:
// collections/Orders.ts
import { CollectionConfig } from 'payload/types';
export const Orders: CollectionConfig = {
slug: 'orders',
hooks: {
afterChange: [
async ({ doc, operation }) => {
if (operation === 'create') {
// Send to Google Analytics Measurement Protocol
await fetch('https://www.google-analytics.com/mp/collect', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
client_id: doc.clientId,
events: [{
name: 'purchase',
params: {
transaction_id: doc.id,
value: doc.total,
currency: 'USD'
}
}]
})
});
}
return doc;
}
]
},
fields: [
// ... your fields
]
};
Block-Based Content Tracking
Track engagement with Payload blocks:
// Track block visibility
function BlockRenderer({ blocks }) {
return blocks.map((block, index) => {
const Component = blockComponents[block.blockType];
return (
<VisibilityTracker
key={block.id} => {
gtag('event', 'block_view', {
block_type: block.blockType,
block_position: index,
block_id: block.id
});
}}
>
<Component {...block} />
</VisibilityTracker>
);
});
}
Rich Text Field Analytics
Track engagement with Payload's rich text:
// Track reading progress through rich text
function RichTextRenderer({ content }) {
const containerRef = useRef(null);
useEffect(() => {
const observer = new IntersectionObserver(
(entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const progress = calculateReadProgress(containerRef.current);
gtag('event', 'reading_progress', {
progress_percent: Math.round(progress * 100)
});
}
});
},
{ threshold: [0.25, 0.5, 0.75, 1] }
);
// Observe content sections
containerRef.current?.querySelectorAll('[data-section]')
.forEach(el => observer.observe(el));
return () => observer.disconnect();
}, [content]);
return <div ref={containerRef}>{serializeRichText(content)}</div>;
}
Integration Best Practices
1. Use Collection IDs
Track using stable Payload document IDs:
gtag('event', 'content_view', {
content_id: doc.id, // Payload's stable ID
collection: 'articles',
content_slug: doc.slug // May change
});
2. Track Draft vs Published
Handle Payload's draft system:
// Check document status
gtag('event', 'content_view', {
content_id: doc.id,
collection: 'articles',
status: doc._status, // 'draft' or 'published'
is_preview: router.query.draft === 'true'
});
3. Leverage Payload Globals
Track global settings and configurations:
// Fetch and track global settings
const settings = await payload.findGlobal({
slug: 'settings'
});
gtag('set', {
site_version: settings.version,
feature_flags: settings.features?.join(',')
});
4. Handle Payload Uploads
Track media and file engagement:
gtag('event', 'file_download', {
file_id: upload.id,
file_name: upload.filename,
file_type: upload.mimeType,
file_size: upload.filesize,
collection: 'media'
});
5. Track Relationships
Monitor content relationships:
// Track related content engagement
gtag('event', 'related_content_click', {
source_id: currentDoc.id,
source_collection: 'articles',
target_id: relatedDoc.id,
target_collection: relatedDoc.relationTo,
relationship_type: 'related_articles'
});
E-commerce Tracking with Payload
For Payload-based e-commerce:
// Track product views
gtag('event', 'view_item', {
currency: 'USD',
value: product.price,
items: [{
item_id: product.id,
item_name: product.title,
item_category: product.category?.title,
price: product.price,
payload_collection: 'products'
}]
});
Testing Integrations
Next.js Development:
- Test in development mode
- Verify admin panel exclusion
- Check SSR vs CSR tracking
Hook Testing:
- Test afterChange hooks
- Verify server-side events
- Monitor hook execution
Block Testing:
- Test block visibility tracking
- Verify block type identification
- Check nested block handling
Next Steps
Choose your integration to get started:
Additional Resources
Payload Documentation: