Implementation Patterns7 min read

How to Use Content Source Maps for Headless Analytics

Your marketing team asks a deceptively simple question: which content drove this conversion?

Published June 21, 2026

Your marketing team asks a deceptively simple question: which content drove this conversion? On a headless stack, the honest answer is usually "we can't tell." The frontend renders a hero headline, a product description, and three reference blocks, but by the time those strings hit the browser they are anonymous text. Nothing connects the rendered pixel back to the document, field, or release it came from. So your analytics measure page URLs and DOM selectors that break the moment a designer reshuffles the layout, and content attribution quietly becomes guesswork.

Sanity is the Content Operating System for the AI era, an intelligent backend that treats every piece of rendered content as something you can trace back to its source. Content Source Maps are the mechanism: an opt-in layer that tags query results with the exact document ID, field path, and dataset each value came from, so the frontend (and your analytics) can reason about content provenance instead of guessing from markup.

This guide is about turning that provenance into headless analytics you can trust. We will cover how Content Source Maps are emitted from GROQ queries, how to decode them on the frontend, how to wire them into click and conversion tracking, and how the same trace data powers Visual Editing and governance. The reframe is simple: stop measuring pages, start measuring content.

Why DOM-based content analytics breaks on a headless stack

The default way teams attribute outcomes to content is to scrape the DOM. You attach an analytics listener to a CSS selector, log the text inside it, and hope nobody touches the markup. On a traditional monolith this is fragile but survivable, because the template, the data, and the page are one artifact. On a headless stack the content lives in one system and the rendering lives in another, so the coupling that DOM scraping assumes simply isn't there.

Consider a homepage hero. Your analytics records that a user clicked a button labeled "Start free trial" inside `.hero__cta`. Next sprint a developer renames the class, an editor A/B tests the label, and a content modeler splits the hero into a reusable reference. Now three independent changes have each silently broken your attribution, and the dashboard keeps reporting numbers that look fine. Worse, you can't answer the question that actually matters: was it the hero copy, the headline variant, or the offer block that moved the metric? The text in the DOM doesn't know which document it came from.

This is the core failure mode. DOM-based analytics measures presentation, not content. It conflates "where something appeared on a page" with "what the content was and where it lives in the system." The fix is to carry provenance from the content store all the way to the rendered string, so attribution survives layout changes, label edits, and content reuse. Content Source Maps exist precisely to thread that needle: they annotate query output with the origin of every value, so the frontend always knows which document and field produced what the user saw and clicked.

What Content Source Maps actually emit

A Content Source Map is metadata that travels alongside a query result. When you run a GROQ query against Content Lake with source maps enabled, the response includes the usual JSON your frontend expects, plus a compact mapping that records, for each value in the result, the document ID, the field path, and the dataset it originated from. Instead of a bare string "Start free trial," you now have a string plus a verifiable pointer back to `hero.cta.label` on a specific document in a specific dataset.

The design matters here. The mapping is structured so it does not bloat every value inline; it uses a path-based scheme that references shared origin entries, which keeps payloads small even for large, deeply nested query results. Because GROQ lets you ask for exactly the shape you need in one round trip, including projections, references resolved with `->`, and array slices with `[...]`, the source map stays aligned with whatever shape you projected. You aren't limited to top-level fields. A value pulled three references deep still carries its true origin.

This is the part teams underestimate. The source map is not a heuristic or a best-effort guess derived from string matching. It is emitted by Content Lake as a direct byproduct of executing the query, so it is as accurate as the query itself. That accuracy is what makes it trustworthy for analytics. You are not inferring provenance after the fact from rendered HTML; you are reading provenance that the content backend computed at query time, which is the difference between attribution you can audit and attribution you have to apologize for.

Decoding source maps on the frontend

Raw source map metadata is not something you hand to a marketer. The frontend's job is to decode it and attach the resolved provenance to the right rendered values, then expose that provenance to whatever consumes it: an analytics SDK, a debugging overlay, or the Presentation Tool. Sanity's client tooling handles the decoding step, walking the mapping and resolving each value's path back to its document ID, field, and dataset so you don't reimplement the path resolution yourself.

The practical pattern is to resolve provenance close to render. When you map over a list of product cards, each card's title and price carry their own origin, so you tag each rendered element with data attributes like the document ID and field path at the moment you render it. That keeps the provenance co-located with the element a user will actually click, which sidesteps the entire class of bugs where a later layout change separates the analytics hook from the content it was supposed to describe.

There is a governance benefit to doing this cleanly. Source map metadata can reveal document IDs and dataset names, so you decide deliberately where it is allowed to flow. Enable it on preview and authenticated internal builds where editors and analysts need it, and gate or strip it on anonymous production traffic if your threat model calls for that. Because the emission is opt-in at query time, you control the blast radius per environment rather than leaking internal identifiers everywhere by default. The point is that decoding is a deliberate, configurable frontend concern, not magic, and treating it that way keeps both your analytics and your security posture honest.

Wiring provenance into click and conversion tracking

Once each rendered element carries its document ID and field path, content analytics stops being a layout problem and becomes a data-modeling problem, which is the good kind. Your click handler reads the provenance attributes off the clicked element and sends them to your analytics pipeline alongside the usual event. Now a conversion isn't attributed to `/pricing#cta-2`; it's attributed to the `offer.headline` field on a specific document. Rename the CSS class, restructure the page, or reuse that offer block on five landing pages, and the attribution still points at the same content.

This unlocks questions DOM analytics can't answer. Because the same document can appear across many pages through references, you can aggregate performance by content rather than by URL. Which headline document drives the most trial starts, regardless of where it is embedded? Which product description correlates with add-to-cart, across every collection page it appears on? You are measuring the content asset, not the coincidence of where it landed. For teams running structured content at scale, that shift from page-centric to content-centric measurement is the whole payoff.

It also tightens the editorial feedback loop. When provenance flows into analytics, you can join engagement data back to the documents in Content Lake and surface it where editors work. Pair that with Content Releases and scheduling, and a team can ship a content change, watch the field-level metrics for the exact documents in that release, and make the next decision on evidence instead of vibes. The content backend becomes the join key for analytics, which is exactly the role a Content Operating System should play: one shared foundation rather than a content silo on one side and an analytics silo on the other.

The same trace data powers Visual Editing

Content Source Maps were not built solely for analytics. The same provenance that lets you attribute a conversion to a field is what lets Sanity's Visual Editing and Presentation Tool turn any rendered value on your live frontend into a direct, click-to-edit entry point back into the Studio. An editor hovers a headline on the real site, clicks, and lands on exactly that field in exactly that document, because the rendered value already knows where it came from.

This is a meaningful reason to invest in source maps even if analytics is your initial driver. The work you do to decode provenance and attach it to rendered elements is largely the same work that enables overlay-based editing. You instrument once and get two capabilities: field-level attribution for analysts, and field-level editing for content teams. Both depend on the same fact, that the frontend can map any visible value back to its origin document and field, which is precisely what DOM scraping could never give you.

It also reframes the headless tradeoff. The historical complaint about going headless is that you lose the in-context editing experience of a coupled CMS, where what you edit is what you see. Source maps plus Visual Editing close that gap without recoupling the stack. The frontend stays a frontend you fully control, the content stays governed in Content Lake, and the bridge between them is provenance metadata rather than a rigid template contract. You keep the composability and the DX of headless, and you get back the in-context experience editors actually want, which is the combination most teams assumed they had to choose between.

Rollout, performance, and governance considerations

Treat source maps as an environment-scoped capability, not a global switch. Enable emission on the queries and environments that need it, preview, authenticated dashboards, internal staging, and the editing overlay, and be deliberate about anonymous production. Because emission is opt-in at the query layer, you don't pay for it on queries that don't need provenance, and you don't ship internal document IDs to environments where you'd rather not. Start narrow, on the handful of high-value surfaces where attribution genuinely changes decisions, then widen as the patterns prove out.

Performance is rarely the blocker people fear. The mapping uses a shared, path-referenced structure rather than repeating origin data inline, so payload growth is modest and proportional to result complexity, not catastrophic. Still, measure it on your real queries. A homepage that resolves dozens of references will carry more mapping than a single blog post, and you should know your numbers before you turn it on broadly. Decode on the client where the data is already in hand, and avoid round-tripping the raw map through services that don't need it.

Governance is where this pays off long term. Field-level provenance gives you an audit trail from a rendered outcome back to a specific document, dataset, and field, which complements Sanity's Roles & Permissions, Content Releases, and Audit logs. On compliance, cite what is real: Sanity maintains SOC 2 Type II, supports GDPR obligations, offers regional hosting and data residency options, and publishes its sub-processor list. Provenance metadata strengthens that story by making content lineage legible, but it is a building block, not a certification. Document where source maps are enabled, who can see decoded IDs, and how the data flows into analytics, and you turn a clever feature into a defensible practice.

Field-level content provenance and in-context editing across headless platforms

FeatureSanityContentfulStoryblokStrapi
Field-level provenance in query resultsContent Source Maps emit document ID, field path, and dataset alongside GROQ results, computed by Content Lake at query time.No equivalent automatic per-value source map; attribution is typically reconstructed on the frontend from known entry IDs and field names.No per-value source map in delivery responses; teams correlate rendered content to stories via known IDs and component data.REST and GraphQL responses return entity data; mapping a rendered value back to a field is left to application code.
Query shape controlGROQ projects exactly the shape you need in one round trip, resolving references with -> and slicing arrays, so the source map matches your projection.GraphQL and REST Delivery API; nested references often require linked includes or multiple queries depending on depth.REST Content Delivery API with resolve_relations for references; shaping deep responses can mean extra params or calls.REST and GraphQL with populate for relations; deeply nested shapes often need explicit population or multiple requests.
Click-to-edit from live frontendVisual Editing and the Presentation Tool use the same source map provenance to jump from a rendered value to its exact field in the Studio.Live Preview and a visual editing SDK enable in-context preview; setup is configured per project rather than derived from a query source map.Visual Editor provides click-to-edit via its bridge and preview tokens, tied to its component model rather than a per-value source map.Preview and draft modes exist; live click-to-edit overlay typically relies on additional plugins or custom integration work.
Content-centric (not page-centric) attributionProvenance lets you aggregate analytics by document and field across every page a referenced asset appears on, not just by URL.Achievable by passing entry IDs into analytics manually; not provided as automatic per-value provenance in responses.Achievable by threading story and component IDs into events; relies on app-side wiring rather than emitted provenance.Achievable by sending entity IDs into your analytics layer; provenance is constructed in application code, not emitted.
Editor customization for provenance-aware workflowsSanity Studio is a React app you ship; custom input components and Structure Builder let you surface field-level data where editors work.Editor is configurable via app framework and UI extensions, but the core editing UI is largely fixed.Editor is configurable with field plugins and blocks; the underlying editing UI is largely fixed.Admin panel is customizable and self-hostable, with strong control over models, though editing UI patterns are more fixed.
Environment-scoped, opt-in emissionSource maps are opt-in per query, so you enable them on preview and internal builds and gate internal IDs on anonymous production traffic.Preview vs delivery is controlled via separate tokens and environments; there is no per-value source map to scope.Draft vs published is controlled via tokens and versions; no per-value source map to enable or gate.Draft and publish states plus environments control exposure; provenance scoping is an application-level concern.

Ready to try Sanity?

See how Sanity can transform your enterprise content operations.