How to Integrate Looker with Your Headless CMS
Connect Looker to a headless CMS so content teams can see fresh content performance, campaign, and experiment data in the dashboards they already use.
What is Looker?
Looker is Google Cloud's business intelligence and data platform for modeling, exploring, and visualizing data from warehouses such as BigQuery, Snowflake, Redshift, and Postgres. Data teams use LookML to define metrics once, then give marketing, product, editorial, and finance teams governed dashboards and self-serve analysis. It's commonly used in mid-market and enterprise analytics stacks where consistent metric definitions matter.
Why integrate Looker with a headless CMS?
Connecting Looker to a headless CMS closes that gap. You can sync structured content metadata into the warehouse Looker already queries, then join it to GA4, Segment, Amplitude, ad spend, CRM, or commerce data. Instead of maintaining a spreadsheet that maps URLs to campaigns every Friday, the mapping comes from the content record itself when it gets published.
Architecture overview
The practical flow is: an editor publishes content in Sanity Studio, the document is written to the Content Lake, and a webhook or Sanity Function fires on that publish event. The handler uses GROQ to fetch only the fields Looker needs, such as slug, title, content type, campaign reference, locale, publish date, author, and experiment ID. Because Looker doesn't ingest arbitrary content records directly, the handler writes those fields into a Looker-connected warehouse table, then calls the Looker API with the SDK to reset a datagroup, refresh cached results, or run a validation query. End users open a Looker Explore, dashboard, or embedded report that joins content metadata to analytics events by slug, canonical content ID, or campaign ID.
Common use cases
Content performance dashboards
Join Sanity content fields with GA4, Segment, or warehouse events so Looker can report traffic, conversions, and revenue by content type, author, campaign, and locale.
Experiment and variant reporting
Sync experiment IDs and variant labels from Sanity so Looker dashboards can compare A/B test outcomes against the exact content variant that shipped.
Editorial planning metrics
Track planned publish dates, actual publish dates, content owners, and status changes in Looker to spot missed deadlines and coverage gaps.
Locale and market reporting
Analyze performance by language, region, product line, and market without asking analysts to infer those dimensions from URL patterns.
Step-by-step integration
- 1
Set up Looker API access
In Looker, create API credentials for a service account with the minimum permissions needed to reset datagroups and read the relevant model. Save the base URL, client ID, client secret, and datagroup ID as environment variables, then install the SDK with npm install @looker/sdk-node.
- 2
Model analytics fields in Sanity Studio
Add fields that analysts can trust, such as slug, canonicalAnalyticsId, campaign, audience, locale, author, publishDate, contentType, and experimentVariant. References are useful here because GROQ can join campaign or author data into a single payload.
- 3
Create a Looker-ready warehouse table
Create a table such as analytics.content_metadata in BigQuery, Snowflake, or Postgres. Include one stable key, usually the Sanity document ID or canonicalAnalyticsId, plus the dimensions you want in Looker.
- 4
Add LookML for the content table
Define a LookML view for the content metadata table, then join it to web events by slug, page path, or canonical content ID. Use a datagroup if you want Looker cache refreshes tied to content publish events.
- 5
Create the sync mechanism
Use a Sanity Function for server-side processing without running your own worker, or use a webhook listener if you already have middleware. Trigger only on published documents, then fetch the final document from the Content Lake with GROQ.
- 6
Test the dashboard path
Publish a test article, confirm the row appears in the warehouse, reset the Looker datagroup, and open the dashboard. Test updates and deletes too, because stale campaign mappings are harder to catch than missing rows.
Code example
import express from 'express'
import {createClient} from '@sanity/client'
import {LookerNodeSDK} from '@looker/sdk-node'
import {BigQuery} from '@google-cloud/bigquery'
const app = express()
app.use(express.json())
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 looker = LookerNodeSDK.init40()
const bigquery = new BigQuery()
app.post('/webhooks/sanity-published', async (req, res) => {
const id = req.body._id?.replace(/^drafts\./, '')
if (!id) return res.status(400).send('Missing document ID')
const doc = await sanity.fetch(`*[_id == $id][0]{
_id, _type, title,
"slug": slug.current,
"campaign": campaign->name,
"author": author->name,
locale, publishDate, experimentVariant
}`, {id})
await bigquery.query({
query: `MERGE analytics.content_metadata T
USING (SELECT @id id, @slug slug, @title title, @type type,
@campaign campaign, @locale locale) S
ON T.id = S.id
WHEN MATCHED THEN UPDATE SET slug=S.slug, title=S.title,
type=S.type, campaign=S.campaign, locale=S.locale
WHEN NOT MATCHED THEN INSERT (id, slug, title, type, campaign, locale)
VALUES (S.id, S.slug, S.title, S.type, S.campaign, S.locale)`,
params: {
id: doc._id,
slug: doc.slug,
title: doc.title,
type: doc._type,
campaign: doc.campaign || null,
locale: doc.locale || 'en-US'
}
})
await looker.ok(looker.reset_datagroup(process.env.LOOKER_DATAGROUP_ID!))
res.status(200).send('Synced content metadata for Looker')
})
app.listen(3000)How Sanity + Looker works
Build your Looker integration on Sanity
Sanity gives you the structured content foundation, real-time event system, GROQ queries, and flexible APIs you need to connect trusted content data with Looker.
Start building free โCMS approaches to Looker
| Capability | Traditional CMS | Sanity |
|---|---|---|
| Analytics-ready content fields | Schemas define analytics fields as typed data, and the Content Lake returns them as queryable JSON for warehouse syncs. | |
| Sync on publish | Webhooks or Functions can trigger on create, update, or delete events, then update the Looker-connected warehouse without polling. | |
| Field-level query control | GROQ can project exact fields and resolve references, such as article, campaign, author, and market, in one query. | |
| LookML fit | Developers can model content around business concepts, then sync flattened rows that match LookML views and joins. | |
| Multi-channel consistency | The same structured record can feed web, mobile, Looker, and AI agents, with analytics IDs built into the schema. |
Keep building
Explore related integrations to complete your content stack.
Sanity + Google Analytics
Connect page and event performance to structured content fields such as content type, campaign, author, and locale.
Sanity + Segment
Send consistent content IDs and campaign properties with customer events so Looker can join behavior to content metadata.
Sanity + Snowflake
Sync structured content into Snowflake tables that Looker can model alongside product, revenue, and customer data.