Features

Features enable you to change your app's behavior from within the GrowthBook UI. For example, turn on/off a sales banner or change the title of your pricing page.

You can set a global value for everyone, use advanced targeting to assign values to users, and run experiments to see which value is better.

Feature Keys

Every feature is defined by a unique key. This is what the engineering team will reference in their code when they check the value of a feature. Feature keys cannot be changed after they are created, so take care when choosing one.

Feature keys must be all lowercase and include only letters, numbers, hyphens, and underscores.

Some examples of good feature keys:

  • onboarding-checklist - ON/OFF flag for a feature
  • checkout-button-color - The color of the checkout button
  • results-per-page - Number of search results to show per page

Feature Types

Features can be a simple ON/OFF flag or a more complex data type (strings, numbers or JSON).

ON/OFF flags can support any of the following use cases:

  • Decouple code deploys and releases
  • Kill switch for production
  • Gradual rollout of features
  • Complex targeting and segmentation of features
  • Validating feature releases with A/B tests

More complex data types enable you to have more than 2 possible states. For example, if you have a checkout button that is currently blue, you could use an ON/OFF flag called new-button-color that sets it to red when ON, but this is pretty limiting since you can't easily try other colors in the future. Instead, if you use a flag button-color and make it a string data type, you can easily set the value to 'blue', 'red', 'green', or any other color without changing your code.

Targeting Attributes

Feature values can be targeted to specific users or groups of users. In order for this to work, you must pass targeting attributes into the GrowthBook SDK and also list them in the GrowthBook App.

This is an example of specifying the targeting attributes in the SDK:

growthbook.setAttributes({
  id: user.id,
  email: user.email,
  country: user.country,
  url: window.location.href,
  userAgent: navigator.userAgent,
  admin: user.isAdmin,
  age: user.age,
});

And this is what it looks like in the GrowthBook App to list them:

List of targeting attributes

Note: The actual values of the targeting attributes (e.g. the user ids, emails, etc.) are never sent to GrowthBook. They are only stored in memory locally within the SDK. This architecture eliminates huge potential security holes and keeps your user's PII safe and secure.

Each attribute has 3 parts:

  • The attribute name itself. This is how the attribute will be referenced in the SDK.
  • The data type of the attribute
    • String - freeform text
    • Number - Floats or integers
    • Boolean - true or false
    • Array of strings - useful for things like "tags"
    • Array of numbers - useful anytime you have multiple numeric values
    • Enum - When there are only a small list of pre-defined values it could take
  • Whether or not it's an identifier. Identifiers are attributes which uniquely identify something - typically either a person, account, company, or device.

Override Rules

Every feature has a default value that is served to all users. The real power comes when you define override rules that let you run experiments and/or change the value based on targeting attributes.

The first matching rule for a user will be applied, so order matters. If there are no matching rules, the default value will be used.

Conditions

Rules can specify conditions to limit which users the rule applies to. These conditions are evaluated against the targeting attributes in the SDK.

There is an easy-to-use UI for simple conditions:

Rule conditions UI

In advanced mode, you can specify conditions using the MongoDB query syntax. This enables you to have nested logic, advanced array operators and more. Here's an example:

// Either the user's name starts with "john"
// OR they are over 65 and have a kid who's a doctor
{
  "$or": [
    {
      "name": { "$regex": "^john" }
    },
    {
      "age": { "$gt": 65 },
      "kids": {
        "$elemMatch": {
          "profession": "doctor"
        }
      }
    }
  ]
}

Note: We use the MongoDB query syntax because it is easy to read and write and is well documented. The conditions are never actually executed against a database. Instead, our SDKs include a light-weight interpreter for this syntax that runs entirely locally.

Force Rules

The simplest type of override rule is a "Force" rule. This forces everyone who matches the condition to get the specified value. For example, you could have a feature default to OFF and use force rules to turn it ON for a specific list of countries.

Force rules are most useful when you want to target a specific value to a subset of users. For example, you want beta users to get the new value and everyone else to get the old value.

Rollout Rules

Percent rollout rules let you gradually release a feature value to a random sample of your users.

Rollouts are most useful when you want to make sure a new feature doesn't break your app or site. You start by releasing to maybe 10% of users. Then after a while if your metrics look ok, you increase to 30% and so on.

For rollout rules, you choose a user attribute to use for the random sample. Users with the same attribute value will be treated the same (either included or not included in the rollout). For example, if you choose a "company" attribute, then multiple employees in the same company will get the same experience.

Experiment Rules

The last type of rule is an Experiment. This randomly splits users into buckets, assigns them different values, and tracks that assignment in your data warehouse or analytics tool.

Experiments are most useful when you aren't sure which value to assign yet.

Here's what an Experiment rule looks like in the GrowthBook UI:

Experiment rule

In the vast majority of cases, you want to split traffic based on either a logged-in user id or some sort of anonymous identifier like a device id or session cookie. As long as the user has the same value for this attribute, they will always get assigned the same variation. In rare cases, you may want to use an attribute such as company or account instead, which ensures all users in a company will see the same thing.

If the total variation percents add up to less than 100%, the remaining users will skip the rule and fall through to the next matching one (or the default value) instead.

You can analyze the result of an Experiment the same way you would any experiment in GrowthBook.

Integration Options

There are few ways to integrate GrowthBook features into your application.

At a high level, the features you create in the GrowthBook UI can be rendered as a single JSON feature definitions file. The contents of this JSON file must be passed into the SDK. Any feature flags evaluated before the JSON file loads will have a value of null, so it's important to load the features quickly.

There is a public GrowthBook API endpoint for the feature definitions JSON:

The API includes a cache header with a 30-second TTL (plus stale-while-revalidate and stale-if-error headers).

Load directly (client-side)

You can use window.fetch (or similar) to fetch from the API when your app first loads on the client:

fetch(`https://cdn.growthbook.io/api/features/${MY_API_KEY}`)
  .then((res) => res.json())
  .then((json) => {
    growthbook.setFeatures(json.features);
  });

For self-hosted deployments, we highly recommend using a CDN like Fastly or CloudFlare. The GrowthBook API alone is not meant to serve large amounts of production traffic.

The downside of this approach is that you're adding a network request into your critical rendering path. If the HTTP request is slow (or if the API crashes) for whatever reason, your user experience will suffer.

Cache in Redis/Memcached/etc.

You can keep your own copy of the feature definition JSON within a cache system like Redis or Memcached. To keep your cache up-to-date, you can poll the GrowthBook API endpoint every few minutes.

There are two benefits to this approach. First, reading from a cache is usually orders of magnitude faster than an HTTP fetch request. Second, you are protected in case the API goes down or is slow for whatever reason.

The downside is that if you currently don't have a cache system, this approach increases complexity of your stack. Also, your polling script to keep the cache up-to-date could fail without you knowing about it and features could get out of date.

Load at build-time

Another approach is to fetch the feature definitions JSON once at build time and bundle them with your app.

This is the fastest approach since it avoids all 3rd party requests entirely. Once your app is built, loading features for each request is almost instant.

The downside is that in order to update feature defintiions, you need to create a new build of your app. If you need to turn off a feature quickly (in case of a bug) this may be a problem.