> ## Documentation Index
> Fetch the complete documentation index at: https://docs.growthbook.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Setting up Contextual Bandits

> Checklist for running a contextual bandit: compatible SDK, tracking callback, and GrowthBook prerequisites.

export const CommercialFeature = ({feature, description}) => {
  const commercialFeatures = {
    "adv-presentations": {
      plan: "enterprise",
      displayName: "Adv Presentations"
    },
    "advanced-permissions": {
      plan: "pro",
      displayName: "Advanced Permissions"
    },
    "ai-byok": {
      plan: "enterprise",
      displayName: "Ai Byok"
    },
    "ai-suggestions": {
      plan: "enterprise",
      displayName: "AI Suggestions"
    },
    archetypes: {
      plan: "pro",
      displayName: "Archetypes"
    },
    "audit-logging": {
      plan: "enterprise",
      displayName: "Audit Logging"
    },
    "cloud-proxy": {
      plan: "pro",
      displayName: "Cloud Proxy"
    },
    "code-references": {
      plan: "pro",
      displayName: "Code References"
    },
    "contextual-bandits": {
      plan: "enterprise",
      displayName: "Contextual Bandits"
    },
    "custom-hooks": {
      plan: "enterprise",
      displayName: "Custom Hooks"
    },
    "custom-launch-checklist": {
      plan: "enterprise",
      displayName: "Custom Launch Checklist"
    },
    "custom-markdown": {
      plan: "enterprise",
      displayName: "Custom Markdown"
    },
    "custom-metadata": {
      plan: "enterprise",
      displayName: "Custom Metadata"
    },
    "custom-roles": {
      plan: "enterprise",
      displayName: "Custom Roles"
    },
    dashboards: {
      plan: "enterprise",
      displayName: "Dashboards"
    },
    "decision-framework": {
      plan: "pro",
      displayName: "Decision Framework"
    },
    "encrypt-features-endpoint": {
      plan: "pro",
      displayName: "Encrypt Features Endpoint"
    },
    "environment-inheritance": {
      plan: "enterprise",
      displayName: "Environment Inheritance"
    },
    "events-forwarder": {
      plan: "pro",
      displayName: "Events Forwarder"
    },
    "experiment-impact": {
      plan: "enterprise",
      displayName: "Experiment Impact"
    },
    "feature-configs": {
      plan: "enterprise",
      displayName: "Feature Configs"
    },
    "funnel-metrics": {
      plan: "pro",
      displayName: "Funnel Metrics"
    },
    "hash-secure-attributes": {
      plan: "pro",
      displayName: "Hash Secure Attributes"
    },
    "historical-power": {
      plan: "pro",
      displayName: "Historical Power"
    },
    holdouts: {
      plan: "enterprise",
      displayName: "Holdouts"
    },
    "incremental-refresh": {
      plan: "enterprise",
      displayName: "Incremental Refresh"
    },
    "json-validation": {
      plan: "enterprise",
      displayName: "JSON Validation"
    },
    "large-saved-groups": {
      plan: "enterprise",
      displayName: "Large Saved Groups"
    },
    learnings: {
      plan: "enterprise",
      displayName: "Learnings"
    },
    livechat: {
      plan: "pro",
      displayName: "Livechat"
    },
    "manage-official-resources": {
      plan: "enterprise",
      displayName: "Manage Official Resources"
    },
    "metric-correlations": {
      plan: "enterprise",
      displayName: "Metric Correlations"
    },
    "metric-effects": {
      plan: "enterprise",
      displayName: "Metric Effects"
    },
    "metric-groups": {
      plan: "enterprise",
      displayName: "Metric Groups"
    },
    "metric-populations": {
      plan: "pro",
      displayName: "Metric Populations"
    },
    "metric-slices": {
      plan: "enterprise",
      displayName: "Metric Slices"
    },
    "multi-armed-bandits": {
      plan: "pro",
      displayName: "Multi Armed Bandits"
    },
    "multi-metric-queries": {
      plan: "enterprise",
      displayName: "Multi Metric Queries"
    },
    "multi-org": {
      plan: "enterprise",
      displayName: "Multi Org"
    },
    "multiple-sdk-webhooks": {
      plan: "pro",
      displayName: "Multiple Sdk Webhooks"
    },
    "no-access-role": {
      plan: "enterprise",
      displayName: "No Access Role"
    },
    "override-metrics": {
      plan: "pro",
      displayName: "Override Metrics"
    },
    "pipeline-mode": {
      plan: "enterprise",
      displayName: "Pipeline Mode"
    },
    "post-stratification": {
      plan: "enterprise",
      displayName: "Post Stratification"
    },
    "precomputed-dimensions": {
      plan: "pro",
      displayName: "Precomputed Dimensions"
    },
    "prerequisite-targeting": {
      plan: "enterprise",
      displayName: "Prerequisite Targeting"
    },
    prerequisites: {
      plan: "pro",
      displayName: "Prerequisites"
    },
    "product-analytics-dashboards": {
      plan: "pro",
      displayName: "Product Analytics Dashboards"
    },
    "project-admin-role": {
      plan: "enterprise",
      displayName: "Project Admin Role"
    },
    "quantile-metrics": {
      plan: "pro",
      displayName: "Quantile Metrics"
    },
    "ramp-schedules": {
      plan: "pro",
      displayName: "Ramp Schedules"
    },
    redirects: {
      plan: "pro",
      displayName: "Redirects"
    },
    "regression-adjustment": {
      plan: "pro",
      displayName: "CUPED"
    },
    releases: {
      plan: "enterprise",
      displayName: "Releases"
    },
    "remote-evaluation": {
      plan: "pro",
      displayName: "Remote Evaluation"
    },
    "require-approvals": {
      plan: "enterprise",
      displayName: "Require Approvals"
    },
    "require-project-for-features-setting": {
      plan: "enterprise",
      displayName: "Require Project For Features Setting"
    },
    "require-project-for-sdk-connections-setting": {
      plan: "enterprise",
      displayName: "Require Project For Sdk Connections Setting"
    },
    "retention-metrics": {
      plan: "pro",
      displayName: "Retention Metrics"
    },
    "safe-rollout": {
      plan: "pro",
      displayName: "Safe Rollout"
    },
    saveSqlExplorerQueries: {
      plan: "pro",
      displayName: "Save SQL Explorer Queries"
    },
    "schedule-feature-flag": {
      plan: "pro",
      displayName: "Schedule Feature Flag"
    },
    "scheduled-revisions": {
      plan: "enterprise",
      displayName: "Scheduled Revisions"
    },
    scim: {
      plan: "enterprise",
      displayName: "SCIM"
    },
    "sequential-testing": {
      plan: "pro",
      displayName: "Sequential Testing"
    },
    "share-product-analytics-dashboards": {
      plan: "enterprise",
      displayName: "Share Product Analytics Dashboards"
    },
    simulate: {
      plan: "pro",
      displayName: "Simulate"
    },
    sso: {
      plan: "enterprise",
      displayName: "SSO"
    },
    "sticky-bucketing": {
      plan: "pro",
      displayName: "Sticky Bucketing"
    },
    teams: {
      plan: "enterprise",
      displayName: "Teams"
    },
    templates: {
      plan: "enterprise",
      displayName: "Templates"
    },
    "unlimited-managed-warehouse-usage": {
      plan: "pro",
      displayName: "Unlimited Managed Warehouse Usage"
    },
    "visual-editor": {
      plan: "pro",
      displayName: "AI Visual Editor"
    }
  };
  const {plan, displayName} = commercialFeatures[feature];
  const isEnterprise = plan === "enterprise";
  const defaultDescription = isEnterprise ? "is available on Enterprise plans." : "is available on Pro and Enterprise plans.";
  const planLabel = isEnterprise ? "Enterprise" : "Pro";
  const containerStyle = isEnterprise ? {
    backgroundColor: "color-mix(in srgb, var(--indigo-a3) 60%, transparent)"
  } : {
    backgroundColor: "color-mix(in srgb, var(--amber-a3) 60%, transparent)"
  };
  const badgeStyle = isEnterprise ? {
    boxShadow: "inset 0 0 0 1px var(--indigo-a8)",
    color: "var(--indigo-a11)"
  } : {
    boxShadow: "inset 0 0 0 1px var(--amber-a8)",
    color: "var(--amber-a11)"
  };
  return <div className="flex items-start gap-2 mb-4 p-3 text-sm leading-[1.4] rounded-lg" style={containerStyle} role="note">
      <span className="inline-flex items-center justify-center px-1.5 h-5 text-xs font-medium rounded-full shrink-0 leading-none" style={badgeStyle}>
        {planLabel}
      </span>
      <div className="flex-1 leading-[1.3]">
        <strong className="font-semibold">{displayName}</strong>{" "}
        {defaultDescription} {description}
      </div>
    </div>;
};

<CommercialFeature feature="contextual-bandits" />

Contextual bandits are in beta for enterprise customers and will evolve with customer feedback.

This page is a checklist of everything you need in place before running your first [Contextual Bandit](/bandits/contextual): upgrading to a compatible SDK, updating your tracking callback, and configuring GrowthBook prerequisites.

## Requirements at a glance

* **An Enterprise plan** — Contextual Bandits are an Enterprise-only beta feature.
* **A supported SDK on a compatible version** — currently JavaScript, React, Node.js, Python, Go, Kotlin (Android), and Swift (iOS) (see below).
* **A connected Data Source** with a warehouse the bandit can query, plus a dedicated Contextual Bandit Assignment Query that selects your user id, timestamp, and one or more context columns (see step 4).
* **A Fact Metric to use as the Decision Metric** — legacy (non-fact) metrics are not supported as decision metrics.

## 1. Install or upgrade to a compatible SDK

Contextual Bandit support ships in the following SDKs:

| SDK | Package | Compatible versions |
| - | - | - |
| JavaScript | `@growthbook/growthbook` | 1.7.0 or higher |
| React | `@growthbook/growthbook-react` | 1.7.0 or higher |
| Node.js | `@growthbook/growthbook` | 1.7.0 or higher |
| Python | `growthbook` | 3.1.0 or higher |
| Go | `github.com/growthbook/growthbook-golang` | 0.5.0 or higher |
| Kotlin (Android) | `io.growthbook.sdk:GrowthBook` | 8.0.0 or higher |
| Swift (iOS) | `growthbook-swift` | 1.2.3 or higher |

Install or upgrade to a compatible version with your package manager:

```bash theme={null}
npm install --save @growthbook/growthbook
# React apps
npm install --save @growthbook/growthbook-react
# Python apps
pip install --upgrade growthbook
# Go apps
go get github.com/growthbook/growthbook-golang@latest
```

Kotlin (Android) and Swift (iOS) are added through Gradle and Swift Package Manager (or CocoaPods) rather than a shell command — see the [Kotlin](/lib/kotlin) and [Swift](/lib/swift) SDK docs for the dependency snippets, and pin at least the version in the table above.

Note: for JS-based SDKs, 1.7 is a backwards-compatible release. Kotlin 8.0.0, by contrast, is a major release: it adds contextual bandit support alongside breaking changes (SDK-owned model types such as `GBContext` and `GBOptions` can no longer be constructed by application code — build a context through `GBSDKBuilder`), so read its [changelog](https://github.com/growthbook/growthbook-kotlin/blob/main/CHANGELOG.md) before upgrading.

<Note>
  **What happens on older SDKs?**

  Contextual bandit rules degrade gracefully. SDKs below the versions listed above (and all SDK languages without the capability) skip contextual bandit rules entirely and serve the feature's default value — they never bucket users with stale or global weights. This makes it safe to roll out a contextual bandit while part of your fleet is still on older SDK versions, but users on those versions won't enter the bandit.
</Note>

## 2. Check your SDK Connection

Two things to verify on your SDK Connection, under **SDK Configuration → SDK Connections** in the GrowthBook app:

1. **Language and version** — make sure the connection's SDK language is one of the languages listed above and its version is set to a compatible version. GrowthBook only includes contextual bandit definitions in the SDK payload for connections that support them.
2. **Cache TTL** — contextual bandits change variation weights while running. If your SDK caches the payload longer than the bandit's update cadence, users will be bucketed with stale weights, which slows learning and can trigger SRM warnings. Make sure your SDK's `maxAge` / TTL settings refresh the payload significantly more often than the bandit reweights (or use streaming updates). The [cache max-age recommendations for multi-armed bandits](/bandits/config#8-prerequisite-double-check-sdk-cache-settings) apply here too.

## 3. Update your tracking callback

Contextual bandits personalize variation weights based on unit attributes that are set on the GrowthBook SDK. The best way to ensure we log the attributes used to bucket units is to use the new `trackingCallback` signature (with a new `user` argument) that allows you to pull the unit attributes that were used at evaluation time. See the [JavaScript SDK docs](/lib/js) for the full callback reference.

Furthermore, for offline debugging and for future health checks in GrowthBook, consider tracking three additional fields: `leafId`, `banditVersion`, and `variationWeights`. These three fields are only set for contextual bandit assignments.

```js theme={null}
const gb = new GrowthBook({
  apiHost: "https://cdn.growthbook.io",
  clientKey: "sdk-abc123",
  trackingCallback: (experiment, result, user) => {
    analytics.track("Experiment Viewed", {
      experimentId: experiment.key,
      variationId: result.key,
      // Directly pass through attributes used for contextual bandits (e.g. userRole)
      deviceId: user.attributes.deviceId,
      userRole: user.attributes.userRole,
      // Contextual bandit fields (undefined for regular experiments)
      leafId: result.leafId,
      banditVersion: result.banditVersion,
      variationWeights: result.variationWeights,
    });
  },
});
```

This change is optional only if you already track user attributes in your tracking callback. We strongly recommend passing the values from the `user.attributes` object to ensure the attributes being logged are the same as the ones being used to bucket users.

## 4. Create a Contextual Bandit Assignment Query

To take advantage of the above attributes and additional contextual bandit fields, you need to create a dedicated **Contextual Bandit Assignment Query**. This query holds the assignment SQL plus the list of context columns the bandit is allowed to split on. Manage these on your GrowthBook Datasource page under **Contextual Bandit Assignment Queries**, or via the [createContextualBanditQuery](/api/ContextualBanditQueries/operation/createContextualBanditQuery) REST endpoint.

The query must select one row per exposure event with the following columns:

```sql theme={null}
SELECT
  device_id,
  timestamp,
  experiment_id,
  variation_id,
  -- Context columns that match the new trackingCallback attribute
  -- columns you're using for personalization, e.g. userRole
  userRole,
  -- Optional contextual bandit health-check columns, logged by your tracking callback
  leaf_id,
  bandit_version,
  variation_weights
FROM contextual_bandit_events
```

Requirements:

* At least one context column (e.g. `userRole`) is required — a bandit with no context to split on is just a multi-armed bandit. It should ideally match the columns you're sending from the `user` argument from your tracking callback. The set passed here must match your SDK Attributes in GrowthBook in order to be used to target the contextual bandit.
* `leaf_id`, `bandit_version`, and `variation_weights` are the warehouse-side counterparts of the tracking callback fields from [step 3](#3-update-your-tracking-callback). They are optional, but you should include them — later versions of contextual bandits will use them for health checks like SRM.

## 5. Create the Contextual Bandit

Create the bandit under **Experiments → Contextual Bandits** in the left navigation. You'll need:

* **A hash attribute** — the attribute used to randomize users into variations, which should map to the user identifier in your assignment query. For short-lived bandits, we suggest a `session_id` or `device_id` that will re-generate on future visits to ensure you get the most power while managing when a session ends yourself.
* **A Contextual Bandit Assignment Query** — the query you created in step 4. The attribute columns you pass there will dictate the attributes that will be used to target the contextual bandit. Future versions of contextual bandits will let you select a subset of these columns to use as targeting attributes.
* **Exploration window and update cadence** — how long the bandit collects data before it starts reweighting, and how often it reweights after that. See the [bandit configuration guide](/bandits/config#4-set-the-exploration-window-and-update-cadence) for guidance.
* **A Decision Metric** — the single Fact Metric the bandit optimizes toward. The same guidance as for [multi-armed bandits](/bandits/config#5-selecting-a-decision-metric) applies: prefer a metric with a short conversion window and a conversion rate that isn't extremely low or high.

This step can also be done via the REST API using the [createContextualBandit](/api/ContextualBandits/operation/createContextualBandit) endpoint.

## 6. Link a Feature Flag and start

Once you've created a contextual bandit, you can link it to a feature flag and start it. A contextual bandit serves its variations through a **contextual bandit rule** on a feature flag, and needs at least one linked flag before it can start:

1. Create (or pick) a feature flag and link it from the bandit's page. This adds a contextual bandit rule to the flag in a draft revision.
2. Define the value each bandit variation should serve.
3. Start the bandit. Pending drafts on linked flags are published automatically when the bandit starts.

Once running, GrowthBook refreshes the bandit on its update cadence: each refresh queries your warehouse, refits the context tree, and pushes updated per-leaf weights to your SDKs. You can watch the current weights and per-context results on the bandit's page.

This step can also be done via the REST API by:

* Creating a feature flag via the [postFeatureV2](/api/features-v2/operation/postFeatureV2) endpoint.
* Linking the feature flag to the contextual bandit via the [addContextualBanditLinkedFeature](/api/ContextualBandits/operation/addContextualBanditLinkedFeature) endpoint.
* Starting the contextual bandit via the [startContextualBandit](/api/ContextualBandits/operation/startContextualBandit) endpoint.

## Next steps

* [Driving a Contextual Bandit via API](/bandits/contextual-via-api) — run the same lifecycle entirely from the REST API, including refreshing and reading results.
* [Contextual Bandits overview](/bandits/contextual) — concepts, and when to use one over a multi-armed bandit.
* [Contextual Bandit technical details](/statistics/contextual-bandit-technical) — how per-context weights are computed.
