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

# Contextual Bandits

> Personalize which variation a user sees based on their context, with a separate set of traffic weights per segment.

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: "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" />

<Badge color="purple">beta</Badge>

### What are Contextual Bandits?

A Contextual Bandit is a [bandit](/bandits/overview) that personalizes which variation a user sees based on their **context** — attributes like country, device, or past behavior. Like a standard multi-armed bandit, traffic weights change while the experiment runs to favor better-performing variations. The difference is that a contextual bandit learns a *separate* set of weights for each context, instead of a single global set of weights for all users.

In other words, a multi-armed bandit asks "which variation is best?" A contextual bandit asks "which variation is best **for this kind of user**?" Where a global winner would be applied to everyone, a contextual bandit can send users in one country to one variation and users in another country to a different one — whatever performs best on the **Decision Metric** within each context.

### When should I run a Contextual Bandit?

You should run a contextual bandit when:

* You expect the **best variation to differ across user segments** (e.g. by region, device, or plan), rather than there being one global winner.
* You have a single, clear **Decision Metric** to optimize toward.
* You can express the segmenting attributes as **columns in your assignment query** so the bandit can split on them.
* You care more about shipping the best experience to each segment than about producing unbiased, per-metric learnings.

### How is it different from a multi-armed bandit?

| Characteristic  | **Multi-Armed Bandit**                      | **Contextual Bandit**                                  |
| --------------- | ------------------------------------------- | ------------------------------------------------------ |
| Goal            | Find the single best variation for everyone | Find the best variation for each user context          |
| Traffic weights | One global set of weights                   | A separate set of weights per context                  |
| Inputs          | Decision Metric only                        | Decision Metric **plus** targeting/context attributes  |
| Best when       | One variation is best for all users         | The best variation varies across segments              |
| Setup           | Standard assignment query                   | Assignment query that also selects the context columns |

### What you need to run one

A contextual bandit splits traffic on **context attributes**, so it needs an assignment query that selects those columns in addition to the user identifier and timestamp. In GrowthBook these are defined on a **Contextual Bandit Query**, which holds the assignment SQL plus the list of `targetingAttributeColumns` the bandit is allowed to split on. Each context column you want to use must appear in the query's `SELECT`, and at least one targeting attribute is required — a bandit with no context to split on is just a multi-armed bandit.

To get started you will need:

* A connected **Data Source** with a warehouse the bandit can query.
* An assignment query that selects your user id, timestamp, and one or more **context columns**.
* A single **Decision Metric** to optimize toward.
* Two or more **variations** to test.
* An **SDK that supports contextual bandits** — version 1.7.0+ of the JavaScript, React, or Node.js SDK.

For a step-by-step checklist covering SDK installation, tracking callback changes, and the rest of the setup, see [Setting up a Contextual Bandit](/bandits/contextual-config).

### GrowthBook's Contextual Bandit implementation

Like multi-armed bandits, contextual bandits use **Thompson sampling**, a Bayesian algorithm that balances *exploration* (trying variations to learn how they perform) and *exploitation* (sending more traffic to the variations that look best). The key addition is that GrowthBook fits a decision tree over your context attributes, partitioning users into **leaves** — groups that share similar context — and then runs Thompson sampling *within each leaf*. This is how the bandit can converge to different winners for different kinds of users.

As with standard bandits, GrowthBook ensures every variation keeps at least a small share of traffic within each context, so the bandit can keep adapting if user behavior changes over time.

The statistical details are covered in the [Contextual Bandit technical reference](/statistics/contextual-bandit-technical).

## FAQ

1. **When is a contextual bandit worth it over a multi-armed bandit?**<br />
   Only when you have a real reason to believe the best variation differs across user segments. If one variation is best for everyone, a contextual bandit adds complexity (splitting your data across contexts, which needs more traffic per context) without a payoff. A plain multi-armed bandit is the better default.

2. **Can I run a Contextual Bandit using the frequentist engine?**<br />
   No. Like multi-armed bandits, contextual bandits are available only under the Bayesian engine, where Thompson sampling is used.

3. **What happens if a context has very little traffic?**<br />
   GrowthBook controls how finely it splits using settings like `minUsersPerLeaf` and `maxLeaves`.

4. **Do contextual bandits suffer from the same biases as multi-armed bandits?**<br />
   Yes — because traffic weights change adaptively, the same [adaptive-experimentation biases](https://arxiv.org/abs/1905.11397) apply, and splitting by context can make them more pronounced in low-traffic segments. If unbiased per-metric effect estimates are your goal, a standard experiment is still the better tool.
