Analytics & Data8 min read

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.

Published April 29, 2026
01 โ€” Overview

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.


02 โ€” The case for integration

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.


03 โ€” Architecture

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.


04 โ€” Use cases

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.


05 โ€” Implementation

Step-by-step integration

  1. 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. 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. 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. 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. 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. 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.


06 โ€” Code

Code example

typescriptsanity-to-looker.ts
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)

07 โ€” Why Sanity

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 โ†’

08 โ€” Comparison

CMS approaches to Looker

CapabilityTraditional CMSSanity
Analytics-ready content fieldsSchemas define analytics fields as typed data, and the Content Lake returns them as queryable JSON for warehouse syncs.
Sync on publishWebhooks or Functions can trigger on create, update, or delete events, then update the Looker-connected warehouse without polling.
Field-level query controlGROQ can project exact fields and resolve references, such as article, campaign, author, and market, in one query.
LookML fitDevelopers can model content around business concepts, then sync flattened rows that match LookML views and joins.
Multi-channel consistencyThe same structured record can feed web, mobile, Looker, and AI agents, with analytics IDs built into the schema.

09 โ€” Next steps

Keep building

Explore related integrations to complete your content stack.

Ready to try Sanity?

See how Sanity's Content Operating System powers integrations with Looker and 200+ other tools.