> ## 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.

# 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: "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.

### 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 plan. 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 multi-armed bandit converges on one winner for 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?

Reach for a contextual bandit when you either want (a) a personalization engine that is driven by your warehouse metrics or (b) want the best performance for a short-lived test (such as marketing or promotional copy) and reasonably believe that performance will vary by the context. It's a good fit when:

* **You expect the winner to differ across segments.** Checkout copy that lands differently by country, a layout that helps on mobile but hurts on desktop, an upsell that converts free-plan users but annoys paying ones. If you already suspect the honest answer is "it depends", a contextual bandit finds *and ships* the per-segment answer in a single run.
* **Those segments are defined by a handful of attributes you know at decision time.** Country, device, plan tier, new vs. returning, acquisition channel. The bandit personalizes on what it knows about the user when it buckets them, not on behavior observed afterwards.
* **Each segment carries meaningful traffic.** Splitting weights by context means each context has to learn from its own data. Segments too small to learn on their own get pooled together and behave like a plain bandit, so a contextual bandit only pays off when the interesting segments are large enough to stand alone.
* **You have a single Decision Metric that moves quickly.** As with multi-armed bandits: one metric, a short conversion window, and a conversion rate that isn't extremely low or high.
* **Shipping the best experience to each segment matters more than clean learnings.** You're continuously optimizing a surface like a homepage CTA, a pricing page, or recommendation copy, not making a one-time ship/no-ship decision that needs unbiased effect estimates.

### When should I run something else?

* **A [multi-armed bandit](/bandits/overview)** when you expect one variation to be best for everyone, or you aren't sure. Splitting by context spreads your data thinner, so if there's no real heterogeneity you pay the cost without the payoff. A plain bandit is the safer default.
* **A standard experiment** when you need unbiased estimates, care about several metrics, or your outcome takes a long time to materialize. If your goal is to *learn* whether segments respond differently (rather than act on it right away), run a standard experiment and analyze results by [dimension](/app/dimensions).

### 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** context attributes |
| Best when | One variation is best for all users | The best variation varies across segments, and each segment has enough traffic to learn |
| Output | A winning variation | A per-segment mapping from context to variation |

### 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).

### Next steps

* [Setting up a Contextual Bandit](/bandits/contextual-config) — the prerequisites checklist: compatible SDK, tracking callback changes, the assignment query, and creating and starting the bandit in the app.
* [Driving a Contextual Bandit via API](/bandits/contextual-via-api) — the same lifecycle run entirely from the REST API.
* [Contextual Bandit technical details](/statistics/contextual-bandit-technical) — how the weights are computed.

## 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't I just run a standard experiment and look at the dimension breakdowns?**<br />
   You can, and that's the right move if your goal is to learn about heterogeneity. But a dimension breakdown tells you *after the fact* which segment preferred which variation, and then you still have to build and ship targeting rules by hand. A contextual bandit does that during the run: it shifts traffic within each segment as it learns, and keeps adapting after you would have stopped the experiment.

3. **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.

4. **What happens if a context has very little traffic?**<br />
   GrowthBook controls how finely it splits users using settings like `maxLeaves`. A segment without enough traffic to learn on its own isn't given its own leaf; it's pooled with similar users and shares their weights.

5. **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.
