How to Integrate Optimizely with Your Headless CMS
Connect Optimizely to structured content so teams can test headlines, offers, pages, and audience-specific experiences without copying content between tools.
What is Optimizely?
Optimizely is a digital experience platform used for web experimentation, feature experimentation, personalization, and digital experience analytics. Product, growth, marketing, and engineering teams use Optimizely to run A/B tests, route traffic, target audiences, and measure conversions across websites and apps. Its experimentation tools are widely used by mid-market and enterprise teams that need controlled rollouts and measurable content or product changes.
Why integrate Optimizely with a headless CMS?
Experimentation gets messy when content lives in one place and test logic lives somewhere else. A landing page headline changes in your content system, but the A/B test in Optimizely still points to last week’s copy. A personalization campaign targets “enterprise visitors,” but the actual offer text is buried in a page field that nobody wants to paste into another UI.
Connecting Optimizely to a headless CMS category system solves that split. With Sanity’s AI Content Operating System, content is structured as typed JSON in the Content Lake, so Optimizely can receive specific fields like headline, CTA label, audience key, slug, experiment key, and variation ID. GROQ selects the exact payload, webhooks fire on publish, and Functions can run the sync logic without a separate worker app.
The alternative is manual work. Someone copies variation copy into Optimizely, another person updates the website, and a third person checks whether the experiment still matches the published page. That can work for one test. It breaks down when you’re running 20 campaigns across 6 locales, 4 audience segments, and multiple frontend surfaces.
Architecture overview
A typical Sanity and Optimizely integration starts with structured content in the Content Lake. Editors create experiment-ready documents in Sanity Studio, for example a campaign with references to variants, audience segments, locale-specific copy, and Optimizely IDs. When a document is published, a Sanity webhook can fire with a GROQ filter such as _type == "experimentVariant" && defined(optimizely.featureId). The webhook calls a Sanity Function or your own middleware. That server-side code fetches the published document from the Content Lake using GROQ, joins referenced audience and campaign documents, maps the result to Optimizely fields, and calls the Optimizely REST API with a bearer token. For Optimizely Feature Experimentation, the sync often updates a feature variable or variation metadata with a Sanity content ID, headline, CTA, or JSON payload. For Optimizely Web Experimentation, the sync may update experiment or variation configuration while the page still renders content from Sanity. At runtime, the frontend uses the Optimizely Web snippet or Feature Experimentation SDK to decide which variation a visitor should see, then renders the matching Sanity content to the end user.
Common use cases
A/B test Sanity-authored page copy
Editors create headline, body, image, and CTA variants in Sanity, while Optimizely assigns traffic and measures conversions.
Personalize offers by audience
Sync audience keys and offer IDs from Sanity to Optimizely so campaigns can target segments like industry, region, plan type, or lifecycle stage.
Run feature-flagged content launches
Use Optimizely Feature Experimentation to decide when a visitor sees a new promo, pricing message, or onboarding flow backed by Sanity content.
Test localized campaigns
Publish locale-specific variants in Sanity and map them to Optimizely experiments for markets such as en-US, fr-FR, and de-DE.
Step-by-step integration
- 1
Set up Optimizely
Create an Optimizely Web Experimentation or Feature Experimentation project, add your environments, and copy the project ID, SDK key, and REST API personal access token. If you’re using Feature Experimentation, create a flag and note the feature ID, variable ID, and variation keys you’ll map to Sanity documents.
- 2
Install the required packages
In your sync service or Sanity Function, install @sanity/client for Content Lake reads. In your frontend, install @optimizely/optimizely-sdk if you’re using Feature Experimentation decisioning, or add the Optimizely Web snippet if you’re running browser-based experiments.
- 3
Model experiment content in Sanity Studio
Create schema fields for experimentKey, featureId, variableId, variationKey, audienceKey, locale, headline, image, CTA label, CTA URL, and status. Keep Optimizely IDs close to the content so editors can see which campaign or flag a variant feeds.
- 4
Create the publish trigger
Add a Sanity webhook with a GROQ filter such as _type == "experimentVariant" && defined(optimizely.featureId). Send published mutations to a Sanity Function or webhook listener, and use a shared secret to verify the request.
- 5
Call the Optimizely API
Fetch the full Sanity document with GROQ, map fields into the shape your Optimizely flag, variable, or experiment expects, and call the Optimizely REST API using a bearer token. Keep large rich text bodies in Sanity when possible, and send content IDs or short JSON values to Optimizely.
- 6
Test the frontend experience
Use Optimizely’s preview or forced-variation tools to check each variant. Then confirm the frontend decision flow: Optimizely returns a variation key, your app fetches the matching Sanity content, and analytics events record the conversion you care about.
Code example
A small webhook handler that receives a Sanity publish event, fetches the current content with GROQ, and updates an Optimizely Feature Experimentation variable through the Optimizely REST API.
import { createClient } from '@sanity/client'
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
})
export async function POST(req: Request) {
const event = await req.json()
const id = event._id.replace('drafts.', '')
const variant = await sanity.fetch(`
*[_id == $id][0]{
_id,
headline,
ctaLabel,
ctaUrl,
variationKey,
optimizely{featureId, variableId}
}
`, { id })
if (!variant?.optimizely?.featureId) {
return Response.json({ skipped: true })
}
const value = JSON.stringify({
sanityId: variant._id,
variationKey: variant.variationKey,
headline: variant.headline,
ctaLabel: variant.ctaLabel,
ctaUrl: variant.ctaUrl
})
const res = await fetch(
`https://api.optimizely.com/v2/features/${variant.optimizely.featureId}/variables/${variant.optimizely.variableId}`,
{
method: 'PATCH',
headers: {
Authorization: `Bearer ${process.env.OPTIMIZELY_TOKEN}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ default_value: value })
}
)
if (!res.ok) {
throw new Error(`Optimizely API error: ${res.status} ${await res.text()}`)
}
return Response.json({ synced: variant._id })
}How Sanity + Optimizely works
Build your Optimizely integration on Sanity
Sanity gives you the structured content foundation, real-time event system, and flexible APIs you need to connect Optimizely experiments with content your teams can actually operate at scale with AI.
Start building free →CMS approaches to Optimizely
| Capability | Traditional CMS | Sanity |
|---|---|---|
| Experiment-ready content structure | Variants often live as duplicated pages or HTML blocks, which makes field-level testing harder. | Schemas define variants, audiences, locales, and Optimizely IDs as typed fields in the Content Lake. |
| Sync on publish | Teams often copy content into Optimizely manually or export updates on a schedule. | Webhooks and Functions can trigger sync logic when content changes, with no polling loop. |
| Field-level payload control | APIs may return page-shaped content, including fields Optimizely doesn’t need. | GROQ can return exactly the experiment payload, including joined references, in one query. |
| Editorial workflow for test variants | Editors may create duplicate pages for each test, which increases cleanup work after the winner is chosen. | Sanity Studio can show experiment fields, previews, tasks, comments, and release planning in one editing workspace. |
| Runtime personalization | The website is often the main delivery target, so reuse across apps and agents takes extra work. | One structured back end can feed web, mobile, Optimizely, and AI agents through APIs and Agent Context. |
Keep building
Explore related integrations to complete your content stack.
Sanity + LaunchDarkly
Pair structured Sanity content with LaunchDarkly flags to control staged launches, beta content, and audience-based rollouts.
Sanity + Dynamic Yield
Send product, campaign, and editorial content from Sanity into Dynamic Yield personalization programs.
Sanity + VWO
Use Sanity-authored variants with VWO tests so marketers can run experiments without duplicating page content.