How to Integrate LaunchDarkly with Your Headless CMS
Connect LaunchDarkly to structured content so product teams can test, target, and ship page variants without waiting on a code release.
What is LaunchDarkly?
LaunchDarkly is a feature management and experimentation platform used by engineering and product teams to control releases with feature flags, targeting rules, and experiments. Teams use it to roll out features by user segment, run A/B tests, kill risky releases quickly, and measure which variation performs better. It's a common choice for companies that need release control across web, mobile, server-side apps, and internal tools.
Why integrate LaunchDarkly with a headless CMS?
Personalization gets messy when flag rules live in one system and the content for each variation lives somewhere else. A product manager might create a LaunchDarkly flag called homepage-hero-test, while an editor keeps variant copy in a spreadsheet, a ticket, or a hardcoded JSON file. That setup works for one test. By the tenth test, nobody knows which headline is live, which audience sees it, or whether the mobile app got the same variant as the website.,Connecting LaunchDarkly to a headless CMS category tool fixes that split. LaunchDarkly decides who sees variation A, B, or C. Your content system provides the structured copy, images, CTA labels, disclaimers, and localization for each variation. With Sanity, that content lives as typed JSON in the Content Lake, so a sync job can send only the fields LaunchDarkly needs, such as flag key, variation keys, headline text, CTA text, locale, and campaign dates.,The alternative is manual coordination. Editors ask developers to update flag payloads. Developers copy text into LaunchDarkly. QA checks three environments because the content source and release rules aren't connected. With real-time webhooks or Functions, a published content update can trigger a sync to LaunchDarkly in seconds. The trade-off is that you need to design the boundary carefully. LaunchDarkly should usually hold compact variation payloads or content IDs, while rich article bodies, Portable Text, and large image metadata should stay in the Content Lake.
Architecture overview
A typical Sanity and LaunchDarkly integration starts when an editor publishes an experiment or personalized content entry in Sanity Studio. A Sanity webhook fires on that publish event, or a Sanity Function runs server-side on the content mutation. The handler uses GROQ to fetch exactly the fields needed for the LaunchDarkly flag, including the LaunchDarkly flag key, variation keys, audience labels, localized headline text, CTA labels, and referenced image URLs. It then calls the LaunchDarkly REST API, commonly PATCH /api/v2/flags/{projectKey}/{flagKey}, with an API access token to update the flag's JSON variations or metadata. On the front end, the LaunchDarkly SDK evaluates the flag for the current context, such as user, account, plan, region, or device. The app renders the returned variation payload directly if it's small, or uses the returned Sanity document ID to fetch the full content from the Content Lake. This keeps targeting decisions in LaunchDarkly and structured content in Sanity, with no polling job or separate sync server required when you use Functions.
Common use cases
Homepage hero experiments
Test three Sanity-authored headlines and CTA labels while LaunchDarkly assigns visitors to variations and tracks results.
Account-based personalization
Serve different product messaging to enterprise, startup, and free-plan accounts using LaunchDarkly context attributes.
Regional campaign rollouts
Publish localized campaign content in Sanity, then use LaunchDarkly rules to release it by country, state, or market.
Safe content launches
Roll out a new onboarding flow to 5 percent of users, watch metrics, then increase traffic without republishing content.
Step-by-step integration
- 1
Set up LaunchDarkly
Create a LaunchDarkly project and environment, then add a feature flag for the experience you want to control, such as homepage-hero-test. Choose a JSON flag if you want the variation to return structured content, or a string flag if it should return a Sanity document ID. Create an API access token for the REST API, and install the client SDK your front end needs, such as launchdarkly-js-client-sdk for browser apps or @launchdarkly/node-server-sdk for server-side evaluation.
- 2
Model experiment content in Sanity Studio
Create a schema for experiment content with fields like launchDarklyFlagKey, variants, variantKey, headline, subhead, ctaLabel, image, locale, and status. Keep the LaunchDarkly variation key stable, because your sync code will use it to match Sanity variants to LaunchDarkly variations.
- 3
Create the publish trigger
Use a Sanity webhook filtered to the experiment document type, or create a Sanity Function that runs on publish events. Configure the webhook projection to include the document ID, for example {"documentId": _id}, so the handler can fetch the latest published document from the Content Lake.
- 4
Fetch the exact payload with GROQ
In the handler, use @sanity/client and GROQ to fetch only the fields LaunchDarkly needs. This is where structured content helps. You don't need to parse HTML or copy values from a rendered page.
- 5
Update LaunchDarkly through its API
Call the LaunchDarkly REST API with your project key, environment key where needed, flag key, and API token. For compact personalization, update JSON variations with headline, CTA, and content ID values. For larger pages, store the Sanity document ID in LaunchDarkly and let the app fetch the full content from Sanity after flag evaluation.
- 6
Test the frontend experience
Initialize the LaunchDarkly SDK with a test context, evaluate the flag, and render the returned variation. Test at least three cases: an anonymous visitor, a targeted account, and the default fallback. Also test what happens when a Sanity document is unpublished or a LaunchDarkly flag is off.
Code example
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 { documentId } = await req.json();
const doc = await sanity.fetch(`*[_id == $id][0]{
launchDarklyFlagKey,
"variants": variants[]{
"key": variantKey,
headline,
ctaLabel,
"contentId": ^._id
}
}`, { id: documentId });
if (!doc?.launchDarklyFlagKey) {
return Response.json({ skipped: true });
}
const variations = doc.variants.map((variant: any) => ({
name: variant.key,
value: variant
}));
const res = await fetch(
`https://app.launchdarkly.com/api/v2/flags/${process.env.LD_PROJECT_KEY}/${doc.launchDarklyFlagKey}`,
{
method: "PATCH",
headers: {
Authorization: process.env.LD_API_TOKEN!,
"Content-Type": "application/json"
},
body: JSON.stringify([
{ op: "replace", path: "/variations", value: variations },
{ op: "replace", path: "/fallthrough/variation", value: 0 }
])
}
);
if (!res.ok) throw new Error(await res.text());
return Response.json({ synced: true, flag: doc.launchDarklyFlagKey });
}How Sanity + LaunchDarkly works
Build your LaunchDarkly integration on Sanity
Sanity gives you the structured content foundation, real-time event system, and flexible APIs to connect LaunchDarkly targeting with content your team can publish and update.
Start building free โCMS approaches to LaunchDarkly
| Capability | Traditional CMS | Sanity |
|---|---|---|
| Experiment variant content | Variants often live inside page fields or plugins, which makes it harder to reuse the same test across web and mobile. | Variants are structured in schema-as-code and can include stable LaunchDarkly keys, localized fields, references, and validation rules. |
| Sync on publish | Publishing usually updates the rendered site first, then teams manually update the flag payload or wait for a plugin. | Webhooks or Functions can react to publish events, fetch the latest document, and call the LaunchDarkly API without a polling job. |
| Payload control | Content may be stored as page HTML, so sending clean JSON to LaunchDarkly can require parsing or custom export code. | GROQ can project exactly the flag payload, including joins across referenced campaign, image, and localization documents. |
| Editor workflow for tests | Editors can publish pages, but experiment setup often depends on developers copying final text into LaunchDarkly. | Sanity Studio can provide fields for flag keys, variation keys, review status, and preview links that match LaunchDarkly test cases. |
| Multi-channel personalization | Tests are often tied to one site or page template. | One structured content model can serve web, mobile, email, LaunchDarkly, and AI agents with the same variant IDs. |
| Operational trade-offs | Less setup for simple page tests, but scaling beyond one channel can create manual work. | Requires schema design and sync logic up front, but gives you clearer ownership of content, flags, and release flow. |
Keep building
Explore related integrations to complete your content stack.
Sanity + Optimizely
Run experiments that combine Optimizely testing with structured content from Sanity for web and product experiences.
Sanity + Dynamic Yield
Connect Sanity-authored product, campaign, and editorial content to Dynamic Yield personalization programs.
Sanity + Uniform
Use Uniform for orchestration and personalization while Sanity supplies structured content for every channel.