Sanity CMS integration

This topic explains how to use the integration between LaunchDarkly and Sanity CMS. The integration connects LaunchDarkly feature flags to Sanity Studio so that content editors can manage A/B/n test variations on individual content fields directly inside Sanity, while LaunchDarkly handles experiment targeting and traffic allocation.

Use the integration to:

  • Add A/B/n test variations to individual content fields in Sanity Studio
  • Sync feature flags and their variations automatically from LaunchDarkly
  • Let content editors author variant content without developer intervention
  • Serve personalized content based on LaunchDarkly targeting rules at runtime
  • Filter which flags appear in the Studio using LaunchDarkly tags

The integration stores variant content in Sanity entries alongside each field’s default value. Your application uses a LaunchDarkly SDK to evaluate the active variation and queries Sanity with that variation to retrieve the correct content.

How the integration works

The integration uses the @sanity/personalization-plugin, a Sanity Studio plugin that enables A/B/n experiments on individual content fields. When you install and configure the plugin, content editors can assign LaunchDarkly feature flags to fields and author variant content directly in the Studio.

When configured, the plugin:

  • Registers new schema types for each field type you enable for experiments. For example, enabling the string type creates flagString and variantString types.
  • Fetches available feature flags and their variations from LaunchDarkly using a Reader API token you configure in the Studio
  • Lets editors assign a flag to a field, then author content for each variation
  • Stores the default value and all variant content on the Sanity entry

All flag targeting rules and traffic allocation remain managed in LaunchDarkly. The integration reads flags but does not create or modify them.

Prerequisites

To use the integration, you must have:

  • A Sanity Studio v3 project
  • A LaunchDarkly account with access to API tokens, projects, and environments
  • Node.js 18 or later
  • A LaunchDarkly API access token with the Reader role, or a custom role with read access to flags

Create or manage API access tokens on the Authorization page. To learn more, read API access tokens.

Set up the Sanity integration

Setting up the Sanity integration involves three steps:

  1. Install the plugin
  2. Configure the plugin
  3. Enable experiment fields

After setup, content editors can add experiments to fields directly in Sanity Studio.

Install the plugin

To install the plugin in your Sanity Studio project, enter the following command in your terminal:

Install personalization-plugin
$npm install @sanity/personalization-plugin

Configure the plugin

Before you configure the plugin, configure your LaunchDarkly flags with string variation types and descriptive variation names. You can also apply tags to filter which flags appear in the Studio.

Here is an example flag configuration:

Example flag configuration
Flag key: homepage-hero-experiment
Variations: control, treatment-a, treatment-b
Tags: sanity-content

To configure the plugin in sanity.config.ts:

  1. Import fieldLevelExperiments from @sanity/personalization-plugin/launchDarkly.
  2. Add the plugin to your Sanity config and provide your LaunchDarkly project key.
  3. Specify the field types you want to enable for experiments.
  4. Optionally, provide tags to filter which flags appear in the Studio.

Here is an example sanity.config.ts:

Example sanity.config.ts
1import { defineConfig } from 'sanity'
2import { fieldLevelExperiments } from '@sanity/personalization-plugin/launchDarkly'
3
4export default defineConfig({
5 // ... your existing Sanity config
6 plugins: [
7 // ... your other plugins
8 fieldLevelExperiments({
9 projectKey: process.env.SANITY_STUDIO_LAUNCHDARKLY_PROJECT_KEY!,
10 fields: ['string', 'text', 'image'],
11 // ... optional filter flags
12 tags: ['sanity-content'],
13 }),
14 ],
15})

Set the project key in your .env file as follows:

Example `.env` file entry
$SANITY_STUDIO_LAUNCHDARKLY_PROJECT_KEY=your-project-key

The API token is stored securely in Sanity using @sanity/studio-secrets and is never exposed to your frontend application.

OptionTypeRequiredDescription
projectKeyStringYesYour LaunchDarkly project key
fieldsArrayYesField types to enable for experiments. Accepts type name strings or complete field definitions.
tagsArrayNoFilter flags to only those with all specified tags

Enable experiment fields

The plugin registers new schema types for each field type you configure. For each type, the plugin creates two new types: one for the flag field and one for each variant entry.

Original typeFlag typeVariant type
stringflagStringvariantString
textflagTextvariantText
imageflagImagevariantImage
Custom typesflag{TypeName}variant{TypeName}

To use experiment-enabled fields, replace the original field type with the corresponding flag type in your schema.

Here is an example schema:

schemas/post.ts
1import { defineField, defineType } from 'sanity'
2
3export const post = defineType({
4 name: 'post',
5 title: 'Post',
6 type: 'document',
7 fields: [
8 defineField({
9 name: 'title',
10 title: 'Title',
11 type: 'flagString', // Enables A/B/n testing on this field
12 }),
13 defineField({
14 name: 'heroImage',
15 title: 'Hero Image',
16 type: 'flagImage', // Enables A/B/n testing on images
17 }),
18 defineField({
19 name: 'excerpt',
20 title: 'Excerpt',
21 type: 'text', // Regular field, no experiments
22 }),
23 ],
24})

Add experiments in Sanity Studio

After configuring the plugin and updating your schema, content editors can add experiments to fields directly in the Studio.

To add an experiment to a field:

  1. Open a document with experiment-enabled fields.
  2. Enter your LaunchDarkly API token when prompted. The Studio stores this token securely. You only need to enter it once.
  3. Select the three-dot field actions menu on an experiment-enabled field.
  4. Select Add flag.
  5. Choose a flag from the menu, which is populated from your LaunchDarkly project.
  6. Add variant content using the quick-add buttons.
  7. Use the Copy default button to duplicate the default field value to a variant as a starting point.

The Studio displays a visual distinction between the default content and each variant.

Stored fields and implementation

This section describes how the integration stores experiment data in Sanity and provides examples for retrieving variant content in your application.

Stored fields on entries

The integration stores the following data on each experiment-enabled field in Sanity:

PropertyTypeDescription
_typeStringSchema type, such as flagString
defaultVariesThe default/control content value
activeBooleanWhether the experiment is active on this field
flagIdStringLaunchDarkly feature flag key
variantsArrayArray of variant content objects

For example, a flagString field with two variants is stored as in this example:

Example stored entry structure
1{
2 "title": {
3 "_type": "flagString",
4 "default": "Welcome to Our Site",
5 "active": true,
6 "flagId": "homepage-hero-experiment",
7 "variants": [
8 {
9 "_key": "abc123",
10 "_type": "variantString",
11 "flagId": "homepage-hero-experiment",
12 "variantId": "control",
13 "value": "Welcome to Our Site"
14 },
15 {
16 "_key": "def456",
17 "_type": "variantString",
18 "flagId": "homepage-hero-experiment",
19 "variantId": "treatment-a",
20 "value": "Discover Something New"
21 }
22 ]
23 }
24}

Each variant object in the variants array contains:

PropertyTypeDescription
_keyStringUnique key for the array element
_typeStringSchema type, such as variantString
flagIdStringLaunchDarkly feature flag key
variantIdStringLaunchDarkly variation value
valueVariesThe variant content value

Implementation examples

These examples show how to evaluate feature flags and retrieve the mapped variant content in an application.

Resolve variant content using GROQ

Use the coalesce function in GROQ (Graph-Relational Object Queries), Sanity’s query language, to retrieve the correct variant content for the active variation. If no variant matches, the query falls back to the default value.

Here is an example of a GROQ query with variant resolution:

GROQ query with variant resolution
*[_type == "post" && slug.current == $slug][0] {
"title": coalesce(
title.variants[flagId == $flagKey && variantId == $variantId][0].value,
title.default
),
"heroImage": coalesce(
heroImage.variants[flagId == $flagKey && variantId == $variantId][0].value,
heroImage.default
),
excerpt,
slug
}

Evaluate a flag and fetch personalized content

This example shows how a Next.js application retrieves the active variation from LaunchDarkly and passes it to Sanity to fetch the matching content:

1import { init, LDClient } from '@launchdarkly/node-server-sdk'
2
3let client: LDClient | null = null
4
5export async function getClient(): Promise<LDClient | null> {
6 if (!client && process.env.LAUNCHDARKLY_SDK_KEY) {
7 client = init(process.env.LAUNCHDARKLY_SDK_KEY)
8 await client.waitForInitialization({ timeout: 10 })
9 }
10 return client
11}
12
13export async function getVariation(
14 flagKey: string,
15 context: { key: string; anonymous?: boolean }
16): Promise<string> {
17 const ldClient = await getClient()
18 if (!ldClient) return 'control'
19
20 const variation = await ldClient.variation(flagKey, context, 'control')
21 return variation.toString()
22}

Handle multiple flags

For documents with multiple experiment fields using different flags, pass each flag key and variant ID separately.

Here is an example:

Example GROQ query with multiple flags
*[_type == "page"][0] {
"headline": coalesce(
headline.variants[flagId == $headlineFlag && variantId == $headlineVariant][0].value,
headline.default
),
"heroImage": coalesce(
heroImage.variants[flagId == $heroFlag && variantId == $heroVariant][0].value,
heroImage.default
),
"ctaText": coalesce(
ctaText.variants[flagId == $ctaFlag && variantId == $ctaVariant][0].value,
ctaText.default
)
}

Troubleshooting

If you encounter issues with the LaunchDarkly Sanity integration, try these solutions.

Flags do not appear in the Studio menu

If no flags appear when adding an experiment to a field:

  1. Verify that your API token has read access to the selected project.
  2. Confirm that projectKey in the plugin config matches your LaunchDarkly project key, not the SDK key.
  3. Verify that the expected flags have all the specified tags applied in LaunchDarkly when using the tags option.
  4. Refresh the Studio page to re-fetch flags from LaunchDarkly.

API token errors

If the Studio displays a token error or repeatedly prompts for a token:

  1. Navigate to a document with experiment-enabled fields.
  2. Enter a new valid LaunchDarkly API token when prompted. The old token is replaced.
  3. Confirm that the token has Reader access or a custom role with read access to flags.

Variant content is not delivered correctly

If your application serves the default content instead of the expected variant:

  1. Confirm that the variantId passed to your GROQ query matches the variation value defined in LaunchDarkly, not the variation name.
  2. Verify that the Sanity document is published. Draft changes are not visible to your frontend.
  3. Confirm that the flagId on the field matches the flag key in your GROQ query.
  4. Clear any CDN caches on your frontend after publishing changes in Sanity.

Schema types are not available

If flagString or other flag types are not available in your schema, confirm that the field type is listed in the fields array in the plugin config. Then restart the Sanity Studio dev server after updating the plugin configuration.