How to Integrate RudderStack with Your Headless CMS
Send publish events and structured content metadata from your headless CMS to RudderStack so product, marketing, and data teams can connect content changes to user behavior.
What is RudderStack?
RudderStack is a customer data platform that collects event data from websites, apps, servers, and cloud tools, then routes it to destinations like warehouses, analytics platforms, and marketing systems. Teams use it to standardize tracking, transform events, and send the same event stream to tools such as Snowflake, BigQuery, Amplitude, Mixpanel, and Google Analytics. Its core value is control over customer data pipelines, especially for teams that want warehouse-first analytics and fewer one-off tracking integrations.
Why integrate RudderStack with a headless CMS?
Content performance usually breaks down at the join. Your analytics tools know that a user viewed a page, clicked a CTA, or started a trial, but they often don't know which content type, campaign, author, region, topic, or experiment variant was involved. That leaves teams stitching reports together with URLs, spreadsheets, and naming conventions that drift after 3 launches.
Architecture overview
A typical Sanity and RudderStack integration starts when an editor publishes content in Sanity Studio. A Sanity webhook fires on the publish mutation and sends the document ID, type, and revision data to a secure endpoint, or to a Sanity Function if you want the logic to run inside Sanity's event system. The handler uses @sanity/client and a GROQ query to fetch the published document from the Content Lake, including only the fields RudderStack needs, such as _id, _type, title, slug, language, campaign reference, topic references, and publish date. The handler then calls RudderStack's Node SDK with a track call, using the source write key and data plane URL from your RudderStack JavaScript or HTTP source. RudderStack receives the event, applies any transformations you've configured, and routes it to destinations like Snowflake, BigQuery, Amplitude, Mixpanel, Google Analytics, or your own webhook destination. On the frontend, the end user reads the published content from Sanity-powered pages, and page or conversion events can include the same contentId so downstream tools can connect content operations to user behavior.
Common use cases
Content performance by taxonomy
Send topic, persona, author, locale, and campaign fields to RudderStack so warehouse reports can compare conversion rates across content groups, not just URLs.
Experiment and variant tracking
Attach Sanity experiment IDs, variant names, and release metadata to RudderStack events so A/B test results include the exact content users saw.
Campaign metadata enrichment
Push campaign and landing page metadata from Sanity into RudderStack, then route it to ad, email, and analytics destinations with consistent fields.
Warehouse-ready content events
Track publish, update, and unpublish events in RudderStack so Snowflake, BigQuery, or Databricks can analyze content velocity alongside product usage.
Step-by-step integration
- 1
Set up RudderStack
Create or open a RudderStack workspace, add a JavaScript, Node.js, or HTTP source, copy the write key, and note your data plane URL, such as https://hosted.rudderlabs.com or your dedicated RudderStack data plane. Add at least one destination, like Snowflake, BigQuery, Amplitude, Mixpanel, or Google Analytics, so you can verify events end to end.
- 2
Define the analytics fields in Sanity Studio
Model the content fields you want in analytics, not just the fields you render. For an article or landing page, that might include title, slug, language, author, topics, campaignId, funnelStage, persona, experimentId, and publishDate. Because Sanity schemas are code, these fields can be reviewed and versioned with the same pull request process as your tracking code.
- 3
Create a publish trigger
Create a Sanity webhook that fires on create, update, and delete events for the document types you want to track. Use a GROQ-powered filter such as _type in ['article', 'landingPage', 'productPage'] to avoid sending unrelated mutations. If you don't want to run a separate server, use a Sanity Function to process the mutation and call RudderStack from server-side code.
- 4
Fetch the full document with GROQ
Webhook payloads should stay small, so use the document ID from the mutation to fetch the current published version from the Content Lake. GROQ can project only the fields RudderStack needs and join references, for example topic titles, author names, and campaign slugs.
- 5
Send events to RudderStack
Install @rudderstack/rudder-sdk-node, initialize it with the RudderStack source write key and data plane URL, and call track with a stable event name like Content Published. Put Sanity document IDs in properties.contentId so frontend events and warehouse queries can join on the same key.
- 6
Test the frontend and warehouse result
Publish a test document in Sanity Studio, confirm the event appears in RudderStack Live Events, then check the destination table or analytics tool. On the frontend, send Page Viewed or CTA Clicked events with the same contentId, slug, and experimentId so you can connect publishing activity to user behavior.
Code example
A small webhook handler that receives a Sanity publish event, fetches the document with GROQ, and sends a RudderStack track event.
import type { VercelRequest, VercelResponse } from '@vercel/node';
import { createClient } from '@sanity/client';
import Analytics from '@rudderstack/rudder-sdk-node';
const sanity = createClient({
projectId: process.env.SANITY_PROJECT_ID!,
dataset: process.env.SANITY_DATASET!,
apiVersion: '2025-01-01',
token: process.env.SANITY_READ_TOKEN!,
useCdn: false
});
const rudder = new Analytics(
process.env.RUDDERSTACK_WRITE_KEY!,
process.env.RUDDERSTACK_DATA_PLANE_URL!
);
export default async function handler(req: VercelRequest, res: VercelResponse) {
if (req.method !== 'POST') return res.status(405).end();
const { _id, transition } = req.body;
if (!_id || transition !== 'appear') return res.status(202).json({ skipped: true });
const doc = await sanity.fetch(`*[_id == $id][0]{
_id,
_type,
title,
'slug': slug.current,
language,
publishDate,
'topics': topics[]->title,
'author': author->name,
campaignId,
experimentId
}`, { id: _id.replace('drafts.', '') });
if (!doc) return res.status(404).json({ error: 'Document not found' });
rudder.track({
userId: 'sanity-system',
event: 'Content Published',
properties: {
contentId: doc._id,
contentType: doc._type,
title: doc.title,
slug: doc.slug,
language: doc.language,
topics: doc.topics,
author: doc.author,
campaignId: doc.campaignId,
experimentId: doc.experimentId,
publishDate: doc.publishDate
}
});
await rudder.flush();
return res.status(200).json({ sent: true });
}How Sanity + RudderStack works
Build your RudderStack integration on Sanity
Sanity gives you the structured content foundation, real-time event system, and flexible APIs to connect content operations with RudderStack analytics pipelines.
Start building free โCMS approaches to RudderStack
| Capability | Traditional CMS | Sanity |
|---|---|---|
| Content metadata for analytics | Often tied to page templates, HTML, or plugins, so data teams may infer metadata from URLs and page titles. | Content Lake stores typed JSON, and GROQ can return analytics-ready fields plus joined references in one query. |
| Publish event tracking | Publish events may be available through plugins, but they often run inside the web stack and are hard to test separately. | Webhooks and Functions can turn publish, update, and delete mutations into RudderStack track calls without polling. |
| Tracking plan consistency | Field names can vary by template or plugin, which makes event payloads drift over time. | Schema-as-code lets developers review analytics fields, such as campaignId and experimentId, alongside frontend tracking changes. |
| Experiment and personalization data | Variant data is often stored in testing tools, not with the content, which makes analysis depend on separate exports. | Variant, release, audience, and campaign fields can live with the content and be sent to RudderStack on publish and view events. |
| Warehouse analysis | Data usually arrives as web analytics events, with limited content context unless teams add custom tags manually. | RudderStack can receive content lifecycle events in real time, then route them to warehouse tables with stable content IDs. |
| Operational trade-offs | Fast to start if a plugin exists, but harder to control event shape across channels. | Requires thoughtful schema and event taxonomy design, but gives you precise query control and event triggers once they're defined. |
Keep building
Explore related integrations to complete your content stack.
Sanity + Google Analytics
Send content IDs, slugs, campaign fields, and experiment metadata to Google Analytics events for page and conversion reporting.
Sanity + Segment
Route Sanity content metadata into Segment tracking plans so product and marketing tools receive consistent content context.
Sanity + Snowflake
Analyze content lifecycle events, user behavior, and conversion data together in Snowflake using stable Sanity document IDs.