Skip to main content
Requires Python 3.9 or above

Installation

Quick Usage

Available starting in version 1.2.0 For improved performance and better resource utilization, especially in async web applications, use the GrowthBookClient class. This approach provides up to 3x better performance by reusing a single client instance across multiple requests instead of creating new instances per request.

Basic Async Usage

For web framework integration examples, see Integration Examples below.

Real-time Feature Updates

The async client supports real-time feature updates using Server-Sent Events:

Concurrency and Thread Safety

The async client is designed to be thread-safe and handle concurrent requests efficiently. You can safely use a single client instance across multiple coroutines:
Note: While the client is thread-safe, you should not share a single UserContext instance across different requests. Create a new UserContext for each request to maintain proper isolation.

Performance Benefits

The GrowthBookClient provides significant performance improvements over the traditional per-request GrowthBook approach:
  • 3x faster feature evaluations due to instance reuse
  • Lower memory usage by sharing feature data across requests
  • Built-in caching with configurable refresh strategies
  • Real-time updates without polling overhead
  • Async/await support for non-blocking operations

Loading Features

There are two ways to load feature flags into the GrowthBook SDK. You can either use the built-in fetching/caching logic or implement your own custom solution.

Built-in Fetching and Caching

Both the async client and traditional client support built-in fetching and caching of feature flags.
For the async client, use GrowthBookClient with Options:

Custom Caching

GrowthBook comes with a custom in-memory cache.
For the traditional client, configure a custom cache globally:

Custom Implementation

If you prefer to handle the entire fetching/caching logic yourself, you can just pass in a dict of features from the GrowthBook API directly into the constructor:
Note: When doing this, you do not need to specify your api_host or client_key and you don’t need to call gb.load_features().

GrowthBook class

The GrowthBook constructor has the following parameters:
  • enabled (bool) - Flag to globally disable all experiments. Default true.
  • attributes (dict) - Dictionary of user attributes that are used for targeting and to assign variations
  • url (str) - The URL of the current request (if applicable)
  • qa_mode (boolean) - If true, random assignment is disabled and only explicitly forced variations are used.
  • on_experiment_viewed (callable) - A function called with keyword arguments experiment, result, and user_context whenever an experiment is run. The parameter names matter — the SDK invokes it with these exact keywords.
  • api_host (str) - The GrowthBook API host to fetch feature flags from. Defaults to https://cdn.growthbook.io
  • client_key (str) - The client key that will be passed to the API Host to fetch feature flags
  • decryption_key (str) - If the GrowthBook API endpoint has encryption enabled, specify the decryption key here
  • cache_ttl (int) - How long to cache features in-memory from the GrowthBook API (seconds, default 60)
  • features (dict) - Feature definitions from the GrowthBook API (only required if client_key is not specified)
  • forced_variations (dict) - Dictionary of forced experiment variations (used for QA)
There are also getter and setter methods for features and attributes if you need to update them later in the request:

Attributes

You can specify attributes about the current user and request. These are used for two things:
  1. Feature targeting (e.g. paid users get one value, free users get another)
  2. Assigning persistent variations in A/B tests (e.g. user id “123” always gets variation B)
Attributes can be any JSON data type - boolean, integer, float, string, list, or dict.
For the async client, attributes are passed via UserContext for each evaluation:

Secure Attributes

When secure attribute hashing is enabled, all targeting conditions in the SDK payload referencing attributes with datatype secureString or secureString[] will be anonymized via SHA-256 hashing. This allows you to safely target users based on sensitive attributes. You must enable this feature in your SDK Connection for it to take effect. If your SDK Connection has secure attribute hashing enabled, you will need to manually hash any secureString or secureString[] attributes that you pass into the GrowthBook SDK. To hash an attribute, use the hashlib library with SHA-256 support, and compute the SHA-256 hashed value of your attribute plus your organization’s secure attribute salt.
For the async client, hash secure attributes before creating the UserContext:

Tracking Experiments

Any time an experiment is run to determine the value of a feature, you want to track that event in your analytics system.
For the async client, you can set up experiment tracking through the Options. Starting in version 2.4.0, the callback may be a coroutine. It is scheduled on the event loop without blocking evaluation, and a tracking callback that raises is retried on the next evaluation of the same experiment/user pair:
You can also use synchronous callbacks with the async client:
The same applies to on_feature_usage and callbacks registered via client.subscribe(). Both accept plain functions or coroutines starting in version 2.4.0. On earlier versions, callbacks must be synchronous; schedule async work manually with asyncio.get_running_loop().create_task(...).

Tracking Plugins

Available starting in version 1.3.0 The Python SDK supports tracking plugins that provide automated event tracking with batching, error handling, and retry logic. This is the recommended approach for production applications as it handles edge cases and provides better reliability than custom tracking callbacks.

Built-in Tracking Plugin

The SDK includes a built-in tracking plugin that automatically batches and sends events:

Multiple Tracking Plugins

You can use multiple tracking plugins to send events to different analytics systems:

Tracking Plugin Benefits

The tracking plugin system provides several advantages over custom tracking callbacks:
  • Automatic Batching: Events are batched together to reduce API calls
  • Error Handling: Failed requests are automatically retried with exponential backoff
  • Non-blocking: Tracking doesn’t block feature evaluations
  • Configurable: Batch sizes, intervals, and retry logic can be customized
  • Multiple Destinations: Send events to multiple analytics systems simultaneously

Working with Traditional Callbacks

Tracking plugins work alongside your existing on_experiment_viewed callbacks:

Using Features

There are 3 main methods for interacting with features.
  • gb.is_on("feature-key") returns true if the feature is on
  • gb.is_off("feature-key") returns false if the feature is on
  • gb.get_feature_value("feature-key", "default") returns the value of the feature with a fallback
In addition, you can use gb.eval_feature("feature-key") to get back a FeatureResult object with the following properties:
  • value - The JSON-decoded value of the feature (or None if not defined)
  • on and off - The JSON-decoded value cast to booleans
  • source - Why the value was assigned to the user. One of unknownFeature, defaultValue, force, or experiment
  • experiment - Information about the experiment (if any) which was used to assign the value to the user
  • experimentResult - The result of the experiment (if any) which was used to assign the value to the user

Type Safety

Available starting in version 3.0.0 The SDK ships inline type hints (PEP 561 py.typed), so once type checking is enabled — mypy your_app/, pyright your_app/, or an IDE with checking on, like VS Code (Pylance) or PyCharm — your GrowthBook calls are checked with no SDK-specific configuration. This also gives coding agents a feedback loop: wrong feature usage comes back as a checker error instead of shipping. All of this happens at check time only — the SDK performs no runtime validation, and code that skips the type checker behaves exactly as before. Basic inference works out of the box — the return type follows the fallback you provide, and experiments infer their result type from the variations:
Misuse is a checker error:
Misspelled keyword arguments to Experiment and FeatureRule are caught too:

Typed Callbacks

The parameter names of a tracking callback are part of its contract — both clients invoke it with keyword arguments (experiment=..., result=..., user_context=...), so implementations must use these exact names. The SDK exports the contracts (TrackingCallback, FeatureUsageCallback, EventLogger for the legacy client; AsyncTrackingCallback, AsyncFeatureUsageCallback, AsyncEventLogger for the async client, whose callbacks may also be coroutines) and checkers verify your implementation against them:
Passing an async def callback to the legacy synchronous client is also a checker error — only the async client schedules coroutines. Legacy camelCase methods (isOn, getFeatureValue, evalFeature, …) are marked with @deprecated, so editors strike them through and point at the snake_case replacements.

Strict Typing (Generated Feature Keys)

For compile-time checking of feature keys and per-key value types — the equivalent of the JS SDK’s GrowthBook<AppFeatures> — generate a typed client from your features JSON. The generator ships inside the SDK (no separate CLI needed):
features.json is the SDK endpoint payload (e.g. https://cdn.growthbook.io/api/features/<client_key>) or a bare {feature_key: definition} map; pass --format payload|map to override auto-detection, and --decryption-key <key> if the endpoint has encryption enabled. The generated subclasses add zero runtime behavior — all checking happens in your type checker and IDE:
For dynamic keys (looping over feature names), the generated FeatureKey Literal is the escape hatch. Note that cast performs no runtime validation — it just silences the checker — so only cast values you know are real feature keys (enumerated from the generated module or your feature payload), never arbitrary input:
Commit the generated file. Its types describe the feature snapshot it was generated from, so they go stale when features change — regenerate in CI and fail the build if the committed file is out of date:
Features whose value type can’t be inferred (no defaultValue and no typed rules) fall back to Any and are listed in a warning at generation time.

Sticky Bucketing

Available starting in version 1.1.0 By default GrowthBook does not persist assigned experiment variations for a user. We rely on deterministic hashing to ensure that the same user attributes always map to the same experiment variation. However, there are cases where this isn’t good enough. For example, if you change targeting conditions in the middle of an experiment, users may stop being shown a variation even if they were previously bucketed into it. Sticky Bucketing is a solution to these issues. You can provide a Sticky Bucket Service to the GrowthBook instance to persist previously seen variations and ensure that the user experience remains consistent for your users. A sample InMemoryStickyBucketService implementation is provided for reference, but in production you will definitely want to implement your own version using a database, cookies, or similar for persistence. Sticky Bucket documents contain three fields
  • attributeName - The name of the attribute used to identify the user (e.g. id, cookie_id, etc.)
  • attributeValue - The value of the attribute (e.g. 123)
  • assignments - A dictionary of persisted experiment assignments. For example: {"exp1__0":"control"}
The attributeName/attributeValue combo is the primary key.
Async sticky bucket services available starting in version 2.4.0The async client supports network-backed sticky bucket stores (Redis, DynamoDB, etc.) without blocking the event loop. Subclass AbstractAsyncStickyBucketService and implement async versions of get_assignments and save_assignments. Optionally override get_all_assignments to batch all lookups for a user into a single round trip (e.g. one Redis MGET):
Existing synchronous AbstractStickyBucketService implementations also work with the async client without changes. Their blocking calls are offloaded to a thread pool so the event loop is never blocked. They share the event loop’s default thread pool, so for network-backed stores an async service is preferred.A few behaviors worth understanding before choosing a store:
  • Reads happen per evaluation. Assignments are fetched from your service for the supplied UserContext on each evaluation (matching the JavaScript SDK’s multi-user client), so assignment changes made by other processes are picked up promptly. Concurrent evaluations for the same user share a single in-flight lookup. If your service lookups are expensive and your traffic concentrates on hot users, you can opt into a small bounded cache with Options(sticky_bucket_cache_ttl=30, sticky_bucket_cache_size=1000) (seconds / max users; disabled by default). The trade-off is that cross-process assignment changes may not be seen until the TTL expires.
  • Writes are fire-and-forget. Evaluation never waits for persistence, and new assignments are immediately visible to later evaluations in the same process. The trade-off is a small durability window: if the process crashes before a background write completes, that assignment is lost. Long-running web services don’t need to do anything; short-lived processes (serverless functions, scripts) should flush before exit:
    await client.close() flushes automatically.
  • Async services must subclass AbstractAsyncStickyBucketService. The client dispatches on the class, so a duck-typed object with async methods that doesn’t inherit from it is treated as a synchronous service and will not work. The synchronous GrowthBook class accepts only synchronous services and raises ValueError if given an async one.

Inline Experiments

Instead of declaring all features up-front and referencing them by ids in your code, you can also just run an experiment directly. This is done with the run method:
For the async client, use await with the run method:
As you can see, there are 2 required parameters for experiments, a string key, and an array of variations. Variations can be any data type, not just strings. There are a number of additional settings to control the experiment behavior:
  • key (str) - The globally unique tracking key for the experiment
  • variations (any[]) - The different variations to choose between
  • seed (str) - Added to the user id when hashing to determine a variation. Defaults to the experiment key
  • weights (float[]) - How to weight traffic between variations. Must add to 1.
  • coverage (float) - What percent of users should be included in the experiment (between 0 and 1, inclusive)
  • condition (dict) - Targeting conditions
  • force (int) - All users included in the experiment will be forced into the specified variation index
  • hashAttribute (string) - What user attribute should be used to assign variations (defaults to “id”)
  • hashVersion (int) - What version of our hashing algorithm to use. We recommend using the latest version 2.
  • namespace (tuple[str,float,float]) - Used to run mutually exclusive experiments.
Here’s an example that uses all of them:

Inline Experiment Return Value

A call to run returns a Result object with a few useful properties:
The inExperiment flag will be false if the user was excluded from being part of the experiment for any reason (e.g. failed targeting conditions). The hashUsed flag will only be true if the user was randomly assigned a variation. If the user was forced into a specific variation instead, this flag will be false.

Example Experiments

3-way experiment with uneven variation weights:
Slow rollout (10% of users who match the targeting condition):
Complex variations:
Assign variations based on something other than user id:

Working with Encrypted Features

The Python SDK supports encrypted feature flags for enhanced security. When encryption is enabled, the feature payload is encrypted before being sent from GrowthBook, and the SDK automatically decrypts it client-side. If you use the typed-client generator, pass the same key as --decryption-key so it can read the encrypted payload.
For the async client, provide the decryption key in the Options:

Environment Variables

You can also set the decryption key via environment variable:

Error Handling

If decryption fails (wrong key, corrupted data, etc.), the SDK will log an error and treat all features as disabled/default values:

Logging

The GrowthBook SDK uses a Python logger with the name growthbook and includes helpful info for debugging as well as warnings/errors if something is misconfigured. Here’s an example of logging to the console

Integration Examples

This section provides practical examples for integrating GrowthBook with popular web frameworks.

Async Web Framework Integration (FastAPI)

The async client works great with modern async web frameworks like FastAPI:

Starlette Integration

Traditional Web Frameworks (Django, Flask, etc.)

For new projects, we recommend using the Async Client instead for better performance. For traditional synchronous web frameworks, you should create a new GrowthBook instance for every incoming request and call destroy() at the end of the request to clean up resources.

Django Integration

In Django, this is best done with a simple middleware:
Then, you can easily use GrowthBook in any of your views:

Flask Integration

Supported Features