# Audit logs Source: https://docs.growthbook.io/account/audit-logs Review a timestamped log of actions taken in GrowthBook, including who changed feature flags, experiments, and settings. Enterprise **Audit Logging** is available on Enterprise plans. GrowthBook keeps an audit log of all actions taken on the platform. You can access this page from the Settings → Log in the left navigation. ![Audit log page](https://mintcdn.com/growthbook-ea15456d/J3C3juKhu0f7KEr_/static/images/using/audit-logs.png?fit=max\&auto=format\&n=J3C3juKhu0f7KEr_\&q=85\&s=332e945f2233ee8a215ecb36ba95ecc4) These logs are useful for auditing what users have done, and figuring out the cause of any issues. These logs are not user editable and the log is exportable. # User & Team Permissions Source: https://docs.growthbook.io/account/user-permissions Assign user and team roles in GrowthBook, from no access to Admin, including custom roles and project-scoped permissions. In the context of GrowthBook, a user, or member, is an individual who has access to your organization's GrowthBook account. Each user is assigned a role that determines the level of access they have within the application. GrowthBook offers a range of roles, from `No Access` to `Admin` and even custom roles, each with a specific set of permissions (see below). GrowthBook's user permissions system allows organizations to define the level of access each user has within the application. This granular control ensures that users can only access the resources they need to perform their job, enhancing security and privacy. > [!NOTE] > > **Plan availability** > > On the Free plan, members can only be assigned the `Admin` role — assigning any other role (including custom roles) requires a Pro or Enterprise account. Pro and Enterprise organizations have full access to role management. Organizations created before this limit was introduced are unaffected, and existing role assignments always keep working. ## Adding Users Team members can be added to your GrowthBook account via the `Settings` → `Members` page. From this main page, you'll see an "Invite Member" button on the right. If you do not see this button, you do not have permission to add members to your organization. Clicking on "Invite Member" will open a modal window from which you can choose some options for the new user. ![Inviting members modal](https://mintcdn.com/growthbook-ea15456d/J3C3juKhu0f7KEr_/static/images/using/invite-member-modal.png?fit=max\&auto=format\&n=J3C3juKhu0f7KEr_\&q=85\&s=368f33b81adc35c395bf4ed38561821d) Each GrowthBook user needs an email address, and you can select what global permissions you want to assign (See below for a full list of permission levels). Inviting a new member to your organization will send an email to that user inviting them to join. It is possible for a user to join more than one organization. If the user is a member to multiple organizations, they will see a drop-down next to their email address on the top right of the page, where they can select the organization they want to work in. ### Environment Specific Limits ![Environment Specific Limits](https://mintcdn.com/growthbook-ea15456d/J3C3juKhu0f7KEr_/static/images/user-permissions-env-specific.png?fit=max\&auto=format\&n=J3C3juKhu0f7KEr_\&q=85\&s=011da5ea34c9c6a11cbc809c83147b46) GrowthBook's user permissions also include environment specific limits. This permission level applies only to `Engineer` and `Experimenter` roles in Pro or Enterprise accounts. It allows you to limit the feature flags and experiments a user can manage to specific environments. For example, you can allow an `Engineer` to create and run experiments in a staging environment, but not in production. ### Project Specific Permissions Besides the global permissions, you can also assign project-specific permissions to users. This allows you to define a user's default role across all projects and select per-project overrides. For example, a new user could be a `collaborator` by default for all projects, but on the `mobile` project, they could be an `experimenter` so they can manage all feature flags and experiments assigned to that project. ### How permissions are evaluated GrowthBook first determines whether the action is scoped to a specific project. **Actions that target a project** (for example, editing a feature flag that belongs to a project) are evaluated using project-specific roles: * If a project-specific role applies (the user's own, or one from any team they're on), only project-specific roles are used for that project; the global role is ignored. The effective permissions are the union of those project-specific roles (the user gets a permission if any of them grants it). * Otherwise, the global role applies - itself the union of the user's global role and the global roles of any teams they're on. Because the global role is ignored whenever a project-specific role applies, a project-specific role can *reduce* access below the global role. For example, a user whose global role is `Engineer` but who has a `Read Only` role on a project is read-only on that project. The exception is a global `Admin`. A member whose global role is `Admin` (directly, as an additional role, or through a team's global role) keeps `Admin` in every project, since they could change their own project roles anyway. Their project-specific roles, and their teams', have no effect there. This applies to members only: an API key's project-specific roles still limit it even when its global role is `Admin`, and so does the scope of a scoped Personal Access Token. **Actions on another project** are evaluated against the user's role in *that* project. Adding a project to a Feature Flag's [Targeting Projects](/features/environments#delivering-a-feature-to-multiple-projects) checks the `Target` permission in the project being added (choosing All Projects checks it unscoped), while editing and publishing the flag itself remain governed by its primary project. **Actions that are not scoped to a project** are evaluated against the global role. This includes organization-level settings and resources that always live at the org level, such as `Dimensions`, `Namespaces`, and `Presentations`. For organizations without a Pro or Enterprise account, permissions are evaluated solely against the global role. > [!NOTE] > > **Default role for self-registered users** > > If your organization has enabled the setting to allow verified users to automatically join your organization, they will receive the `Collaborator` role by default when they join. However, you can change your organizations' default role at the bottom of the Team page. ### Self-registering and Automatic Approvals If users create an account with a verified email address that matches the domain of your account owner, they will be presented with an option to join your organization. If you have not selected `Automatically approve new verified users` (which is the default), those users will be listed at the bottom of the page under a section called `Pending Members`. From this list, you'll have the option of approving or deleting these self-registered users. ![Self-registering and Automatic Approvals Toggle](https://mintcdn.com/growthbook-ea15456d/J3C3juKhu0f7KEr_/static/images/using/auto-approve-members.png?fit=max\&auto=format\&n=J3C3juKhu0f7KEr_\&q=85\&s=9f1ff84dc25ea5a290b9890684815b86) If you are the account owner, you will see a toggle at the top of the members' page that allows you to automatically approve new members who match your domain. This means that instead of being placed in your `pending members` list, they will automatically join your organization. ### Removing Users To remove a user from your organization, you can click on the three dots next to their name and select `Remove User`. This will remove the user from your organization and revoke their access to all projects and resources within your organization. ## Permissions Fine-tuning user permissions in an application like GrowthBook ensures a tailored experience, empowering organizations to grant precisely defined access levels. This granular control not only enhances security but also enables teams to collaborate efficiently while safeguarding sensitive features or data. Organizations using GrowthBook have a number of different ways of defining a user's permission level, depending on the organization's plan. Regardless of the plan, all organizations can assign a global role when inviting a user which defines their permissions across all projects. If you have a Pro or Enterprise account, you can also assign project-level roles for each user. For example, you can assign a user the global role of `Collaborator`, allowing them to view features and experiments, add comments, and contribute ideas. You can then assign them an `Experimenter` role for a specific project, which allows them to create and run experiments, but only for that project. And, for our Enterprise organizations, we offer the ability to create `Teams`, which are groups of users, all of which inherit the roles and permissions of the Team they're on. The table below lists the roles available in GrowthBook and their associated permissions. | | No Access | Read Only | Collaborator | Engineer | Analyst | Experimenter | Project Admin | Admin | | :-: | :-: | :-: | :-: | :-: | :-: | :-: | :-: | :-: | | Feature Flags | - | View | View Comment | View Comment Add Edit | View Comment | View Comment Add Edit | View Comment Add Edit | View Comment Add Edit | | Experiments | - | View | View Comment | View Comment Edit | View Comment Add Edit Run Queries | View Comment Add Edit Run Queries | View Comment Add Edit Run Queries | View Comment Add Edit Run Queries | | Metrics | - | View | View | View | View Add Edit | View Add Edit | View Add Edit | View Add Edit | | Dimensions | - | View | View | View | View Add Edit | View Add Edit | View Add Edit | View Add Edit | | Segments | - | View | View | View | View Add Edit | View Add Edit | View Add Edit | View Add Edit | | Datasources | - | View | View | View | View Edit\* | View Edit\* | View Edit\* | View Add Edit | | Ideas | - | View | View Add Edit | View Add Edit | View Add Edit | View Add Edit | View Add Edit | View Add Edit | | SDK Connections | - | View Add | View Add | View Add Edit | View Add Edit | View Add Edit | View Add Edit | View Add Edit | | Attributes | - | View | View | View Add Edit | View | View Add Edit | View Add Edit | View Add Edit | | Namespaces | - | View | View | View Add Edit | View | View Add Edit | View Add Edit | View Add Edit | | Environments | - | View | View | View Add Edit | View | View Add Edit | View Add Edit | View Add Edit | | Saved Groups | - | View | View | View Add Edit | View | View Add Edit | View Add Edit | View Add Edit | | Tags | - | - | - | View Add Edit | View Add Edit | View Add Edit | View Add Edit | View Add Edit | | Product Analytic Dashboards | - | View | View | View | View Add Edit | View | View | View Add Edit | | Slack Integration | - | - | - | - | - | - | - | View Add Edit | | Manage Projects | - | - | - | - | - | - | Yes | Yes | | Manage Team | - | - | - | - | - | - | - | Yes | | Manage Plan | - | - | - | - | - | - | - | Yes | | Manage Billing | - | - | - | - | - | - | - | Yes | \**Limited to editing a subset of data source settings - identifier types, experiment assignment queries, Jupyter Notebook queries and Data Pipeline settings. Editing datasource name, projects, description, and connection parameters requires Admin permissions.* ### How does the No Access role work? Enterprise **No Access Role** is available on Enterprise plans. In some cases, an organization might want to hide certain projects from a user entirely. This is possible with the `No Access` role. The `No Access` role can either be applied as the user's global role, or it can be a project-specific role. In the event you want a user to have access to a subset of projects, you can give them a global role of `No Access` and then give project-specific permissions for the projects you want them to be able to view. If, however, you only want to hide a certain project from a user, you can assign their project-level role of `No Access` which will make it so the user isn't able to view the project. If you need to apply these rules to many users at once, you can create a Team with the permissions needed, and then add users to that team. Keep in mind how project-level and global roles combine when a user is on one or more teams: * For a project where the user (or any team they are on) has an **explicit** project-level role, their access to that project is the union of those explicit project-level roles. Global roles are **not** used for that project. * For a project where no explicit project-level role applies, the user's global role (merged with their teams' global roles) is used instead. So to hide a specific project from a user, make sure no explicit project-level role grants them access to it: set their own project-level role for that project to `No Access`, and do the same for any team they are on that would otherwise grant access to it. This doesn't work for a global `Admin`, since their project-level roles have no effect. To hide a project from everyone except the people working on it, you don't need to assign `No Access` roles user by user — restrict access on the project itself (see below). ### Restricting access to a Project Pro **Advanced Permissions** is available on Pro and Enterprise plans. By default, a user's global role applies to every project they don't have an explicit project-level role on. If you want a project to be private — visible only to the people granted a role on it — enable **Restrict user access** on the project's **Roles & Permissions** tab. When a project restricts access, members can only see or act on it if they have an explicit role on that project, assigned directly or through a team. Everyone else is treated as `No Access` for that project: it disappears from their project list, and its feature flags, experiments, and other resources are hidden. Members whose global role includes the `manageTeam` permission (such as Admins) always keep access, and SDKs continue to receive the project's feature flags as normal. The **Roles & Permissions** tab shows each member's effective role on the project, so you can see exactly who retains access, and lets project admins grant project roles without needing org-wide permissions. If you enable Restrict user access before granting yourself a role and you are not an Admin, you will lose access to the project too — GrowthBook warns you before saving. The setting is also available on the [REST API](/api#tag/projects) as the `restrictAccess` field on projects. ### Letting other projects' flags target a project Feature Flags owned by one project can be delivered into another through [Targeting Projects](/features/environments#delivering-a-feature-to-multiple-projects). Two things govern that, both on the receiving project: * **Permission.** Adding a project as a Targeting Project requires the `Target` permission (the `FlagsTarget` policy, included in `FlagsFullAccess`) in that project. Grant it on the receiving project, directly or through a team, to let flags from elsewhere reach it without granting anything on its own flags. Choosing All Projects requires `Target` with no project restriction. Only additions are checked: removing a Targeting Project takes nothing, and a draft can put back a Targeting Project the flag already had. * **Allowing targeting from other projects.** Each project's **Roles & Permissions** page also has an **Allow targeting from other Projects** setting, on by default. Turning it off refuses new targeting (and All Projects) regardless of permission. Flags that already target the project keep doing so. Available on the [REST API](/api#tag/projects) as the `allowTargeting` field on projects. ### How does the Project Admin role work? Enterprise **Project Admin Role** is available on Enterprise plans. The Project Admin role provides full access to all features and experiments within a project, similar to the `Experimenter` role. Additionally, Project Admin allows you to manage project settings and change project roles for other members within that project. This makes it ideal for delegating project-level administration without granting full `Admin` permissions. The Project Admin role can be applied at either the global or project level. When applied globally, the user has Project Admin permissions across all projects. When applied at the project level, the user has Project Admin permissions only for that specific project. Key differences from the `Admin` role: * Project Admin can manage project settings and project member roles, but cannot manage organization-wide settings * Project Admin can create and manage [project-scoped teams](#project-scoped-teams) for the projects they administer * Project Admin cannot add or remove users, manage user's global roles, manage other teams, manage billing, or organization-level configurations > [!NOTE] > > **Organization-level resources are not project-specific** > > Within GrowthBook, not all resources are project-specific. For example, `Dimensions`, `Namespaces`, and `Presentations` all live at the organization level. This means that all users, regardless of role, will be able to view these resources. > > If security of these resources is paramount to your organization, we recommend creating a separate organization, and keeping the resources you want to hide in that organization. ### Teams Enterprise organizations using GrowthBook can create Teams with distinct capabilities. When setting up a Team, you have the option to define both a global role and project-level roles, much like how you do for individual users. Once a Team is established, multiple users can be added to it. Any user added to a Team will automatically inherit all permissions assigned to that Team. This feature becomes particularly useful when combined with [GrowthBook's SCIM integration](/integrations/scim), enabling automated user provisioning and de-provisioning. To create a Team, you can go to `Settings` → `Members` → `Team` via the Sidebar and then select the `Teams` tab at the top of the page. Here, you can create and configure various Teams, before adding members to a Team. When evaluating whether or not a user has permission to perform a certain action, we will merge the user's permissions with the permissions inherited from all the Teams the user is on. So if the user's global role is `Collaborator` but they're on a Team that grants them `Engineer` permissions, that user's permission will then be a merger of the `Collaborator` and `Engineer` roles. #### Project-scoped teams A team whose global role is `No Access` and which has no additional global roles grants nothing outside its project roles. Anyone with the `manageProjects` permission (the `Project Admin` role, or the `Projects Full Access` and `Project Admin Access` policies) can manage these teams without `Team Management` permission, both in the app and over the [REST API](/api#tag/teams): * Create a team with the `noaccess` global role and project roles on projects they administer * Add, change, or remove the team's project role on a project they administer * Add and remove members, and delete the team, when every project role on the team is on a project they administer Changing a team's global role, or managing a team that carries one, still requires `Team Management Full Access`. A caller relying on project permissions alone cannot change their own team membership or their own project roles. > [!NOTE] > > A project role, whether assigned directly or through a team, replaces the member's global role for that project. Adding an `Engineer` to a project-scoped team with a `Read Only` role on a project makes them read-only there. A global `Admin` is the exception: project roles have no effect on them. See [How permissions are evaluated](#how-permissions-are-evaluated). ### Custom roles Enterprise organizations using GrowthBook also have the added flexibility of defining custom roles, which enable organizations to fine-tune a role's permissions. These custom roles can be used just like a standard role and can be applied to users and teams at both the global and project levels. A custom role can also be set as your organization's default role, so if you have auto-join enabled, new members will automatically receive the organization's default role, even if it is a custom role. When creating a custom role, you can either create a role from scratch or duplicate an existing role and then update the role's description along with the policies, which control the role's permissions. Once created, the name of a custom role cannot be changed. If you need to change the name, you will need to duplicate the role and set the new name before saving. Once saved, you'll need to update users to use this new role. #### Policies & Permissions When creating and editing custom roles, organizations have the ability to select specific policies for each role, where the policy contains the underlying permissions. Below, we've outlined the current policies and their associated permissions. If your use case is not met with the current policies, please let us know by creating a [Github Issue](https://github.com/growthbook/growthbook/issues). | Policy Group | Policy | Description | Permissions | | - | - | - | - | | **Global** | ReadData | View all resources - features, metrics, experiments, data sources, etc. | readData | | | Comments | Add comments to any resource | readData, addComments | | **Feature Flags, Configs, and Constants** | FlagsFullAccess | Perform every lifecycle action listed below for Feature Flags, Configs, and Constants | readData, createFeatures, createConfigs, createConstants, deleteFeatures, deleteConfigs, deleteConstants, editFeatureDrafts, editConfigDrafts, editConstantDrafts, reviewFeatures, reviewConfigs, reviewConstants, publishFeatures, publishConfigs, publishConstants, revertFeatures, revertConfigs, revertConstants, targetFeatures | | | ↳ FlagsCreate | Create new Feature Flags, Configs, and Constants. Enabling a new Feature Flag in an environment also requires Publish access to that environment. | readData, createFeatures, createConfigs, createConstants | | | ↳ FlagsEditDrafts | Create and edit drafts, request review, and discard drafts without changing live behavior | readData, editFeatureDrafts, editConfigDrafts, editConstantDrafts | | | ↳ FlagsReview | Approve a draft or request changes | readData, reviewFeatures, reviewConfigs, reviewConstants | | | ↳ FlagsPublish | Change live behavior by publishing a draft, saving directly, unarchiving a resource, or toggling an environment. Direct saves also require Edit access. | readData, publishFeatures, publishConfigs, publishConstants | | | ↳ FlagsRevert | Restore a previously published revision | readData, revertFeatures, revertConfigs, revertConstants | | | ↳ FlagsDelete | Archive a live resource or permanently delete one that is already archived | readData, deleteFeatures, deleteConfigs, deleteConstants | | | ↳ FlagsTarget | Allowed to include this role's project in any Feature Flag's "Targeting Projects". When the role is granted globally, also allowed to set "Targeting Projects" to All Projects. | readData, targetFeatures | | | FlagsBypassApprovals | Publish without an approval, force-publish an out-of-date draft, or unlock a Config. Validation and Custom Hooks still run unless the caller explicitly uses the corresponding REST override. | readData, bypassApprovalFeatures, bypassApprovalConfigs, bypassApprovalConstants | | | ArchetypesFullAccess | Create, edit, and delete saved User Archetypes for Feature Flag debugging | readData, manageArchetype | | **Saved Groups** | SavedGroupsFullAccess | Perform every lifecycle action listed below for Saved Groups | readData, createSavedGroups, deleteSavedGroups, editSavedGroupDrafts, reviewSavedGroups, publishSavedGroups, revertSavedGroups | | | ↳ SavedGroupsCreate | Create new Saved Groups | readData, createSavedGroups | | | ↳ SavedGroupsEditDrafts | Create and edit drafts, request review, and discard drafts without changing the live Saved Group | readData, editSavedGroupDrafts | | | ↳ SavedGroupsReview | Approve a draft or request changes | readData, reviewSavedGroups | | | ↳ SavedGroupsPublish | Publish a draft, save directly, or unarchive a Saved Group. Direct saves also require Edit access. | readData, publishSavedGroups | | | ↳ SavedGroupsRevert | Restore a previously published revision | readData, revertSavedGroups | | | ↳ SavedGroupsDelete | Archive a Saved Group or permanently delete one that is already archived | readData, deleteSavedGroups | | | SavedGroupsBypassApprovals | Publish without an approval or force-publish an out-of-date draft | readData, bypassApprovalSavedGroups | | | BypassSavedGroupSizeLimit | Exceed the organization's size limits for a Saved Group | readData, bypassSavedGroupSizeLimit | | **Experiments** | ExperimentsFullAccess | Create, edit, and delete experiments. Does not include AI Visual Editor access. | readData, createAnalyses, runQueries | | | ExperimentsPublish | Start and stop experiments, which changes what is sent to SDKs. | readData, runExperiments | | | VisualEditorFullAccess | Use the AI Visual Editor to implement experiment changes. | readData, manageVisualChanges | | | SuperDeleteReports | Delete custom reports made by other users. Typically assigned to admins only. | readData, superDeleteReport | | | TemplatesFullAccess | Create, edit, and delete experiment templates | readData, manageTemplates | | | HoldoutsFullAccess | Create, edit, and delete holdouts | readData, createAnalyses, runQueries | | **Metrics and Data** | DataSourcesFullAccess | Create, edit, and delete data sources | readData, createDatasources, editDatasourceSettings, runQueries | | | DataSourceConfiguration | Edit existing data source configuration settings (identifier types, experiment assignment queries) | readData, editDatasourceSettings, runQueries | | | RunQueries | Execute queries against data sources. Required to refresh experiment results. Does not include SQL Explorer access. | readData, runQueries | | | SqlExplorerFullAccess | Create, run, edit, and delete SQL Explorer queries | readData, runSqlExplorerQueries | | | MetricsFullAccess | Create, edit, and delete regular metrics (does not include Fact Metrics) | readData, createMetrics, runQueries, createMetricGroups | | | FactTablesFullAccess | Create, edit, and delete fact tables, metrics, and filters. | readData, manageFactTables, manageFactMetrics, manageFactFilters, runQueries | | | FactMetricsFullAccess | Create, edit, and delete fact metrics and filters. | readData, manageFactMetrics, manageFactFilters, runQueries | | | DimensionsFullAccess | Create, edit, and delete dimensions | readData, createDimensions, runQueries | | | SegmentsFullAccess | Create, edit, and delete segments | readData, createSegments, runQueries | | | PopulationsFullAccess | Create, edit, and delete populations | readData, managePopulations, runQueries | | | ManageOfficialResources | Create, edit, and delete official resources such as Fact Tables, Metrics, and Segments. | readData, manageOfficialResources, runQueries | | **Management** | IdeasFullAccess | Create, edit, and delete ideas | readData, createIdeas | | | PresentationsFullAccess | Create, edit, and delete presentations | readData, createPresentations | | **Product Analytic Dashboards** | GeneralDashboardsFullAccess | Create, edit, and delete Product Analytics dashboards. | readData, manageGeneralDashboards | | **Session Replay** | SessionReplayViewAccess | View and play back recorded user sessions. | readData, viewSessionReplay | | | SessionReplayFullAccess | View, play back, and delete recorded user sessions (single, bulk, and DSR-driven deletions). | readData, viewSessionReplay, deleteSessionReplay | | **SDK Configuration** | SDKConnectionsFullAccess | Create, edit, and delete SDK Connections | readData, manageSDKConnections, manageSDKWebhooks | | | AttributesFullAccess | Create, edit, and delete targeting attributes | readData, manageTargetingAttributes | | | EnvironmentsFullAccess | Create, edit, and delete environments | readData, manageEnvironments | | | NamespacesFullAccess | Create, edit, and delete namespaces | readData, manageNamespaces | | **Settings** | GeneralSettingsFullAccess | Edit organization general settings | readData, organizationSettings | | | NorthStarMetricFullAccess | Configure North Star metrics | readData, manageNorthStarMetric | | | TeamManagementFullAccess | Invite users, delete users, change user roles, add/remove users from teams. | readData, manageTeam | | | CustomRolesFullAccess | Create, edit, and delete custom roles | readData, manageTeam, manageCustomRoles | | | CustomFieldsFullAccess | Create, edit, and delete custom fields | readData, manageCustomFields | | | ProjectsFullAccess | Create, edit, and delete projects, change project roles for other members, and manage project-scoped teams. Can be applied at the global or project level. | readData, manageProjects, createProjects, deleteProjects | | | ProjectAdminAccess | Manage project settings, change project roles for other members, and manage project-scoped teams. | readData, manageProjects | | | TagsFullAccess | Create, edit, and delete tags | readData, manageTags | | | APIKeysFullAccess | Perform every API Key action listed below. Not required to create your own Personal Access Tokens. | readData, viewApiKeys, createApiKeys, editApiKeys, deleteApiKeys, managePersonalAccessTokens | | | ↳ APIKeysView | View secret keys and their audit logs. Revealing a key's value requires Create. | readData, viewApiKeys | | | ↳ APIKeysCreate | Create secret keys, reveal their values, and change a key's role, scope, requester settings, or confirmations | readData, viewApiKeys, createApiKeys | | | ↳ APIKeysEdit | Change a secret key's description and expiration, and re-enable disabled keys | readData, viewApiKeys, editApiKeys | | | ↳ APIKeysDelete | Disable and delete secret keys, and apply the expiration policy to existing keys | readData, viewApiKeys, deleteApiKeys | | | ↳ APIKeysMemberTokens | View, disable, or re-enable any member's Personal Access Tokens, see their audit logs, and apply the expiration policy to existing tokens. Token values stay visible only to their owners. | readData, managePersonalAccessTokens | | | IntegrationsFullAccess | Set up and configure integrations - GitHub, Vercel, etc. | readData, manageIntegrations | | | EventWebhooksFullAccess | Create, edit, and delete event-based webhooks. Used for Slack/Discord notifications. | readData, manageEventWebhooks, viewAuditLog | | | BillingFullAccess | View and edit license key. View invoices and update billing info. | readData, manageBilling | | | AuditLogsFullAccess | View and export audit logs | readData, viewAuditLog | | | DecisionCriteriaFullAccess | Create, edit, and delete decision criteria, part of the experiment decision framework. | readData, manageDecisionCriteria | | | CustomHooksFullAccess | Create, edit, and delete Global, Project, and Config-scoped custom hooks. A hook scoped to a single Feature Flag instead takes edit rights on that flag (FlagsEditDrafts). | readData, manageCustomHooks | #### Choosing individual permissions You do not have to grant **Full access**. Expand that policy in the role editor to select only the actions a role needs. For example, a role with **Edit** but not **Publish** can prepare drafts but cannot change live behavior. Feature Flags, Configs, and Constants share the same set of policies. Create, Review, Publish, Revert, and Archive & delete respect a role's environment restrictions. GrowthBook checks only the environments affected by the action. For example, archiving a Feature Flag checks the environments where that flag is currently enabled. Permanently deleting an already archived resource does not require access to any environment. For Review, an approval counts only for the environments the reviewer can review — see [Environment-scoped approvals](/features/publishing-and-approval-flows#environment-scoped-approvals). Some changes are not environment-specific at all. A Constant's base value and a Config that is not scoped to particular environments apply everywhere rather than to any one environment, so there is no environment for GrowthBook to check. A role with Publish for the Project can publish them regardless of its environment restrictions. Moving a resource to another Project requires the relevant permission in both the current and destination Projects. Event webhooks also respect a role's Project and environment restrictions. A role limited to specific environments can only view and manage webhooks whose environment filter includes only those environments. Webhooks that notify for all Environments, and webhook secrets, which are shared by every webhook, require access that is not limited by environment. API Keys Full Access expands the same way. Create includes revealing key values, because a new key's value is shown by revealing it. Changing a key's role, scope, requester settings, or confirmations also takes Create, since it can give a key more access than it had. Disabling a secret key takes Disable & delete, while re-enabling one takes Edit, since it restores access. Other members' Personal Access Tokens are separate: Manage members' tokens covers seeing, disabling, and re-enabling them, and grants nothing over secret keys. #### Deactivating Roles As we do not support the ability for an organization to delete a standard role, we have introduced the ability for enterprise organizations to deactivate both standard and custom roles. When a role is deactivated, we remove the role from the roles dropdown when adding a new user or updating an existing user's role. If you deactivate a role that is assigned to a user, the user will experience no changes to their permission level. The deactivation of the role simply removes it from the role options. The only guardrail in place around deactivating roles is that you cannot deactivate your organization's default role. # Aggregated Fact Table Source: https://docs.growthbook.io/api/AggregatedFactTable_model # Analytics Exploration Source: https://docs.growthbook.io/api/AnalyticsExploration_model # Fetch values for Product Analytics string columns Source: https://docs.growthbook.io/api/AnalyticsExplorations/operation/getProductAnalyticsColumnValues openapi.yaml POST /v1/product-analytics/column-values # List columns available to a Product Analytics exploration Source: https://docs.growthbook.io/api/AnalyticsExplorations/operation/getProductAnalyticsColumns openapi.yaml GET /v1/product-analytics/columns # Get a Product Analytics exploration Source: https://docs.growthbook.io/api/AnalyticsExplorations/operation/getProductAnalyticsExploration openapi.yaml GET /v1/product-analytics/explorations/{id} # Create a Data Source based visualization Source: https://docs.growthbook.io/api/AnalyticsExplorations/operation/postDataSourceExploration openapi.yaml POST /v1/product-analytics/data-source-exploration # Run a Fact Table based visualization Source: https://docs.growthbook.io/api/AnalyticsExplorations/operation/postFactTableExploration openapi.yaml POST /v1/product-analytics/fact-table-exploration # Run a Funnel based visualization Source: https://docs.growthbook.io/api/AnalyticsExplorations/operation/postFunnelExploration openapi.yaml POST /v1/product-analytics/funnel-exploration # Run a User Journey based visualization Source: https://docs.growthbook.io/api/AnalyticsExplorations/operation/postJourneyExploration openapi.yaml POST /v1/product-analytics/journey-exploration # Create a Metric based visualization Source: https://docs.growthbook.io/api/AnalyticsExplorations/operation/postMetricExploration openapi.yaml POST /v1/product-analytics/metric-exploration # Create a SQL based visualization Source: https://docs.growthbook.io/api/AnalyticsExplorations/operation/postSqlExploration openapi.yaml POST /v1/product-analytics/sql-exploration # Search Product Analytics resources Source: https://docs.growthbook.io/api/AnalyticsExplorations/operation/searchProductAnalyticsResources openapi.yaml GET /v1/product-analytics/search # Archetype Source: https://docs.growthbook.io/api/Archetype_model # Attribute Source: https://docs.growthbook.io/api/Attribute_model # Auto Run Source: https://docs.growthbook.io/api/AutoRun_model # Record something an auto run created Source: https://docs.growthbook.io/api/AutoRuns/operation/appendAutoRunArtifact openapi.yaml POST /v1/auto-runs/{id}/artifacts # Create a single autoRun Source: https://docs.growthbook.io/api/AutoRuns/operation/createAutoRun openapi.yaml POST /v1/auto-runs # Get a single autoRun Source: https://docs.growthbook.io/api/AutoRuns/operation/getAutoRun openapi.yaml GET /v1/auto-runs/{id} # Get all autoRuns Source: https://docs.growthbook.io/api/AutoRuns/operation/listAutoRuns openapi.yaml GET /v1/auto-runs # Update a single autoRun Source: https://docs.growthbook.io/api/AutoRuns/operation/updateAutoRun openapi.yaml PUT /v1/auto-runs/{id} # Code Ref Source: https://docs.growthbook.io/api/CodeRef_model # Config Key Usage Source: https://docs.growthbook.io/api/ConfigKeyUsage_model # Config Lineage Source: https://docs.growthbook.io/api/ConfigLineage_model # Config References Source: https://docs.growthbook.io/api/ConfigReferences_model # Config Revision Activity Log Entry Source: https://docs.growthbook.io/api/ConfigRevisionActivityLogEntry_model # Config Revision Ref Source: https://docs.growthbook.io/api/ConfigRevisionRef_model # Config Revision Review Source: https://docs.growthbook.io/api/ConfigRevisionReview_model # Config Revision Source: https://docs.growthbook.io/api/ConfigRevision_model # Config Schema Export Source: https://docs.growthbook.io/api/ConfigSchemaExport_model # Config Schema Source Source: https://docs.growthbook.io/api/ConfigSchemaSource_model # Config Schema Verify Source: https://docs.growthbook.io/api/ConfigSchemaVerify_model # Config Schema Warning Source: https://docs.growthbook.io/api/ConfigSchemaWarning_model # Config Source: https://docs.growthbook.io/api/Config_model # Confirmation Held Source: https://docs.growthbook.io/api/ConfirmationHeld_model # Confirmation Source: https://docs.growthbook.io/api/Confirmation_model # Constant References Source: https://docs.growthbook.io/api/ConstantReferences_model # Constant Revision Activity Log Entry Source: https://docs.growthbook.io/api/ConstantRevisionActivityLogEntry_model # Constant Revision Ref Source: https://docs.growthbook.io/api/ConstantRevisionRef_model # Constant Revision Review Source: https://docs.growthbook.io/api/ConstantRevisionReview_model # Constant Revision Source: https://docs.growthbook.io/api/ConstantRevision_model # Constant Source: https://docs.growthbook.io/api/Constant_model # Create a single contextualBanditQuery Source: https://docs.growthbook.io/api/ContextualBanditQueries/operation/createContextualBanditQuery openapi.yaml POST /v1/contextual-bandit-queries # Delete a single contextualBanditQuery Source: https://docs.growthbook.io/api/ContextualBanditQueries/operation/deleteContextualBanditQuery openapi.yaml DELETE /v1/contextual-bandit-queries/{id} # Get a single contextualBanditQuery Source: https://docs.growthbook.io/api/ContextualBanditQueries/operation/getContextualBanditQuery openapi.yaml GET /v1/contextual-bandit-queries/{id} # Get all contextualBanditQueries Source: https://docs.growthbook.io/api/ContextualBanditQueries/operation/listContextualBanditQueries openapi.yaml GET /v1/contextual-bandit-queries # Update a single contextualBanditQuery Source: https://docs.growthbook.io/api/ContextualBanditQueries/operation/updateContextualBanditQuery openapi.yaml PUT /v1/contextual-bandit-queries/{id} # Contextual Bandit Query Source: https://docs.growthbook.io/api/ContextualBanditQuery_model # Contextual Bandit Source: https://docs.growthbook.io/api/ContextualBandit_model # Link a feature to a Contextual Bandit Source: https://docs.growthbook.io/api/ContextualBandits/operation/addContextualBanditLinkedFeature openapi.yaml POST /v1/contextual-bandits/{id}/linked-feature/{featureId} Adds a `contextual-bandit-ref` rule to the bottom of the feature's rule list and links the feature to this contextual bandit. The rule lands in a draft revision that auto-publishes when the contextual bandit starts, unless `autoPublish` is set. Targeting (condition, Saved Groups, prerequisites, coverage) is inherited from the contextual bandit and cannot be set on the rule. # Cancel a running Contextual Bandit snapshot refresh Source: https://docs.growthbook.io/api/ContextualBandits/operation/cancelContextualBandit openapi.yaml POST /v1/contextual-bandits/{id}/cancel # Create a single contextualBandit Source: https://docs.growthbook.io/api/ContextualBandits/operation/createContextualBandit openapi.yaml POST /v1/contextual-bandits # Delete a single contextualBandit Source: https://docs.growthbook.io/api/ContextualBandits/operation/deleteContextualBandit openapi.yaml DELETE /v1/contextual-bandits/{id} # Unlink a feature from a Contextual Bandit Source: https://docs.growthbook.io/api/ContextualBandits/operation/deleteContextualBanditLinkedFeature openapi.yaml DELETE /v1/contextual-bandits/{id}/linked-feature/{featureId} Removes every `contextual-bandit-ref` rule pointing at this contextual bandit from the feature and drops the feature from the bandit's linked-feature list. The rule removal lands in a draft revision unless `autoPublish` is set. When the feature has no such rule left, only the linkage is cleared. # Get a single contextualBandit Source: https://docs.growthbook.io/api/ContextualBandits/operation/getContextualBandit openapi.yaml GET /v1/contextual-bandits/{id} # Get current Contextual Bandit leaf weights and latest event Source: https://docs.growthbook.io/api/ContextualBandits/operation/getContextualBanditCurrentWeights openapi.yaml GET /v1/contextual-bandits/{id}/current # Get a single Contextual Bandit weight-update event Source: https://docs.growthbook.io/api/ContextualBandits/operation/getContextualBanditEvent openapi.yaml GET /v1/contextual-bandits/{id}/events/{eventId} # Get features linked to a Contextual Bandit Source: https://docs.growthbook.io/api/ContextualBandits/operation/getContextualBanditLinkedFeatures openapi.yaml GET /v1/contextual-bandits/{id}/linked-features Returns the features that reference this contextual bandit via a `contextual-bandit-ref` rule, enriched with each feature's live/draft state, per-environment rule state, and variation values. Same payload the GrowthBook UI uses to render the Linked Features section. # Get latest Contextual Bandit results Source: https://docs.growthbook.io/api/ContextualBandits/operation/getContextualBanditResults openapi.yaml GET /v1/contextual-bandits/{id}/results Returns the latest contextual-bandit stats engine output (per-context responses tagged with their leaf, the per-leaf targeting conditions, and per-leaf aggregated stats), the overall (marginal) variation weights across all contexts, the health checks (traffic, SRM, multiple exposures) computed by that same run, and the status of the most recent snapshot run for the contextual bandit. Same payload the GrowthBook UI uses to render the contextual bandit results and health tabs. # Get a single Contextual Bandit snapshot Source: https://docs.growthbook.io/api/ContextualBandits/operation/getContextualBanditSnapshot openapi.yaml GET /v1/contextual-bandits/{id}/snapshots/{snapshotId} # Get Contextual Bandit run status Source: https://docs.growthbook.io/api/ContextualBandits/operation/getContextualBanditStatus openapi.yaml GET /v1/contextual-bandits/{id}/status Returns just the status of the most recent snapshot run for the contextual bandit (id, status, queries, timestamps). A lightweight alternative to the full results endpoint, intended for polling while a run is in progress. # List Contextual Bandit weight-update events Source: https://docs.growthbook.io/api/ContextualBandits/operation/listContextualBanditEvents openapi.yaml GET /v1/contextual-bandits/{id}/events # List Contextual Bandit snapshots Source: https://docs.growthbook.io/api/ContextualBandits/operation/listContextualBanditSnapshots openapi.yaml GET /v1/contextual-bandits/{id}/snapshots # List visual changesets for a Contextual Bandit Source: https://docs.growthbook.io/api/ContextualBandits/operation/listContextualBanditVisualChangesets openapi.yaml GET /v1/contextual-bandits/{id}/visual-changesets # Get all contextualBandits Source: https://docs.growthbook.io/api/ContextualBandits/operation/listContextualBandits openapi.yaml GET /v1/contextual-bandits # Create a visual changeset for a Contextual Bandit Source: https://docs.growthbook.io/api/ContextualBandits/operation/postContextualBanditVisualChangesets openapi.yaml POST /v1/contextual-bandits/{id}/visual-changesets # Trigger a Contextual Bandit snapshot refresh Source: https://docs.growthbook.io/api/ContextualBandits/operation/refreshContextualBandit openapi.yaml POST /v1/contextual-bandits/{id}/refresh # Start a Contextual Bandit Source: https://docs.growthbook.io/api/ContextualBandits/operation/startContextualBandit openapi.yaml POST /v1/contextual-bandits/{id}/start # Stop a Contextual Bandit Source: https://docs.growthbook.io/api/ContextualBandits/operation/stopContextualBandit openapi.yaml POST /v1/contextual-bandits/{id}/stop # Update a single contextualBandit Source: https://docs.growthbook.io/api/ContextualBandits/operation/updateContextualBandit openapi.yaml PUT /v1/contextual-bandits/{id} # Replace a Contextual Bandit's rule on a linked feature Source: https://docs.growthbook.io/api/ContextualBandits/operation/updateContextualBanditLinkedFeature openapi.yaml PUT /v1/contextual-bandits/{id}/linked-feature/{featureId} Replaces every `contextual-bandit-ref` rule pointing at this contextual bandit on the feature, keeping each rule's id and position in the rule list. Every field is replaced, so omitted optional fields revert to their defaults. Returns a 400 when the feature has no such rule on the target revision, or when it has several that are not identical to each other. The change lands in a draft revision that auto-publishes when the contextual bandit starts, unless `autoPublish` is set. Targeting (condition, Saved Groups, prerequisites, coverage) is inherited from the contextual bandit and cannot be set on the rule. # Add or remove Contextual Bandit variations Source: https://docs.growthbook.io/api/ContextualBandits/operation/updateContextualBanditVariations openapi.yaml POST /v1/contextual-bandits/{id}/variations Adds and/or removes variations on a Contextual Bandit. Send `addVariations` and `removeVariationIds` independently; both are optional. New arms must carry a `values` entry for each linked feature. Running CBs publish the linked-feature updates; draft CBs stage them until start. Under an approval flow, unapproved drafts leave the added arm `pending` (zero weight, filtered from the SDK) until every linked feature's draft is live. Removed arms are tombstoned; their ids can never be re-added. Weights are reconciled server-side. # Custom Field Source: https://docs.growthbook.io/api/CustomField_model # Create a single customField Source: https://docs.growthbook.io/api/CustomFields/operation/createCustomField openapi.yaml POST /v1/custom-fields # Delete a single customField Source: https://docs.growthbook.io/api/CustomFields/operation/deleteCustomField openapi.yaml DELETE /v1/custom-fields/{id} # Get a single customField Source: https://docs.growthbook.io/api/CustomFields/operation/getCustomField openapi.yaml GET /v1/custom-fields/{id} # Get all custom fields Source: https://docs.growthbook.io/api/CustomFields/operation/listCustomFields openapi.yaml GET /v1/custom-fields # Update a single customField Source: https://docs.growthbook.io/api/CustomFields/operation/updateCustomField openapi.yaml PUT /v1/custom-fields/{id} # Custom Hook Source: https://docs.growthbook.io/api/CustomHook_model # Dashboard Source: https://docs.growthbook.io/api/Dashboard_model # Create a single dashboard Source: https://docs.growthbook.io/api/Dashboards/operation/createDashboard openapi.yaml POST /v1/dashboards **Deprecated.** Use [POST /v2/dashboards](#operation/createDashboardV2) instead. This endpoint does not accept an `owner` and always assigns the dashboard to the authenticated user. A dashboard created with an organization secret API key, which has no associated user, therefore has no owner. The v2 endpoint accepts an `owner` (userId or email) and requires one when authenticating with an organization secret API key. # Create a single dashboard Source: https://docs.growthbook.io/api/Dashboards/operation/createDashboardV2 openapi.yaml POST /v2/dashboards # Delete a single dashboard Source: https://docs.growthbook.io/api/Dashboards/operation/deleteDashboard openapi.yaml DELETE /v1/dashboards/{id} # Get a single dashboard Source: https://docs.growthbook.io/api/Dashboards/operation/getDashboard openapi.yaml GET /v1/dashboards/{id} # Get all dashboards for an experiment Source: https://docs.growthbook.io/api/Dashboards/operation/getDashboardsForExperiment openapi.yaml GET /v1/dashboards/by-experiment/{experimentId} # Get all dashboards Source: https://docs.growthbook.io/api/Dashboards/operation/listDashboards openapi.yaml GET /v1/dashboards # Update a single dashboard Source: https://docs.growthbook.io/api/Dashboards/operation/updateDashboard openapi.yaml PUT /v1/dashboards/{id} # Data Source Source: https://docs.growthbook.io/api/DataSource_model # Dimension Source: https://docs.growthbook.io/api/Dimension_model # Environment Source: https://docs.growthbook.io/api/Environment_model # Event User Source: https://docs.growthbook.io/api/EventUser_model # Experiment Rule Source: https://docs.growthbook.io/api/Experiment Rule_model # Experiment Analysis Settings Source: https://docs.growthbook.io/api/ExperimentAnalysisSettings_model # Experiment Decision Framework Settings Source: https://docs.growthbook.io/api/ExperimentDecisionFrameworkSettings_model # Experiment Metric Override Entry Source: https://docs.growthbook.io/api/ExperimentMetricOverrideEntry_model # Experiment Metric Source: https://docs.growthbook.io/api/ExperimentMetric_model # Experiment Results Source: https://docs.growthbook.io/api/ExperimentResults_model # Experiment Snapshot Source: https://docs.growthbook.io/api/ExperimentSnapshot_model # Experiment Template Source: https://docs.growthbook.io/api/ExperimentTemplate_model # Bulk create or update experiment templates Source: https://docs.growthbook.io/api/ExperimentTemplates/operation/bulkImportExperimentTemplates openapi.yaml POST /v1/experiment-templates/bulk-import # Create a single experimentTemplate Source: https://docs.growthbook.io/api/ExperimentTemplates/operation/createExperimentTemplate openapi.yaml POST /v1/experiment-templates # Delete a single experimentTemplate Source: https://docs.growthbook.io/api/ExperimentTemplates/operation/deleteExperimentTemplate openapi.yaml DELETE /v1/experiment-templates/{id} # Get a single experimentTemplate Source: https://docs.growthbook.io/api/ExperimentTemplates/operation/getExperimentTemplate openapi.yaml GET /v1/experiment-templates/{id} # Get all experimentTemplates Source: https://docs.growthbook.io/api/ExperimentTemplates/operation/listExperimentTemplates openapi.yaml GET /v1/experiment-templates # Update a single experimentTemplate Source: https://docs.growthbook.io/api/ExperimentTemplates/operation/updateExperimentTemplate openapi.yaml PUT /v1/experiment-templates/{id} # Experiment With Enhanced Status Source: https://docs.growthbook.io/api/ExperimentWithEnhancedStatus_model # Experiment Source: https://docs.growthbook.io/api/Experiment_model # Fact Metric Source: https://docs.growthbook.io/api/FactMetric_model # Fact Table Column Source: https://docs.growthbook.io/api/FactTableColumn_model # Fact Table Filter Source: https://docs.growthbook.io/api/FactTableFilter_model # Fact Table Source: https://docs.growthbook.io/api/FactTable_model # Feature Base Rule Source: https://docs.growthbook.io/api/FeatureBaseRule_model # Feature Contextual Bandit Ref Rule Source: https://docs.growthbook.io/api/FeatureContextualBanditRefRule_model # Feature Definition Source: https://docs.growthbook.io/api/FeatureDefinition_model # Feature Environment V1 Source: https://docs.growthbook.io/api/FeatureEnvironmentV1_model # Feature Environment V2 Source: https://docs.growthbook.io/api/FeatureEnvironmentV2_model # Feature Experiment Ref Rule Source: https://docs.growthbook.io/api/FeatureExperimentRefRule_model # Feature Experiment Rule Source: https://docs.growthbook.io/api/FeatureExperimentRule_model # Feature Force Rule Source: https://docs.growthbook.io/api/FeatureForceRule_model # Feature Revision Ref Source: https://docs.growthbook.io/api/FeatureRevisionRef_model # Feature Revision Summary Source: https://docs.growthbook.io/api/FeatureRevisionSummary_model # Feature Revision V1 Source: https://docs.growthbook.io/api/FeatureRevisionV1_model # Feature Revision V2 Source: https://docs.growthbook.io/api/FeatureRevisionV2_model # Feature Rollout Rule Source: https://docs.growthbook.io/api/FeatureRolloutRule_model # Feature Rule V1 Source: https://docs.growthbook.io/api/FeatureRuleV1_model # Feature Rule V2 Source: https://docs.growthbook.io/api/FeatureRuleV2_model # Feature Safe Rollout Rule Source: https://docs.growthbook.io/api/FeatureSafeRolloutRule_model # Feature V1 Source: https://docs.growthbook.io/api/FeatureV1_model # Feature V2 Source: https://docs.growthbook.io/api/FeatureV2_model # Feature With Revisions V1 Source: https://docs.growthbook.io/api/FeatureWithRevisionsV1_model # Feature With Revisions V2 Source: https://docs.growthbook.io/api/FeatureWithRevisionsV2_model # Holdout Source: https://docs.growthbook.io/api/Holdout_model # Create a single holdout Source: https://docs.growthbook.io/api/Holdouts/operation/createHoldout openapi.yaml POST /v1/holdouts Creates a Holdout. The Holdout starts in the `draft` stage. Use the start endpoint to start it. # Get a single holdout Source: https://docs.growthbook.io/api/Holdouts/operation/getHoldout openapi.yaml GET /v1/holdouts/{id} # Get all holdouts Source: https://docs.growthbook.io/api/Holdouts/operation/listHoldouts openapi.yaml GET /v1/holdouts # Start the Holdout's Active Period Source: https://docs.growthbook.io/api/Holdouts/operation/startHoldout openapi.yaml POST /v1/holdouts/{id}/start Feature Flags and Experiments can be added during this period while the Holdout measures their cumulative impact. # Start the Holdout's Analysis Period Source: https://docs.growthbook.io/api/Holdouts/operation/startHoldoutAnalysis openapi.yaml POST /v1/holdouts/{id}/start-analysis Move the holdout into an analysis phase. New Feature Flags and Experiments can no longer be added, but existing traffic splits remain active for existing and new traffic. Results exclude data from before the analysis period so you can measure the cumulative impact after changes are frozen. # Stop a Holdout Source: https://docs.growthbook.io/api/Holdouts/operation/stopHoldout openapi.yaml POST /v1/holdouts/{id}/stop # Update a single holdout Source: https://docs.growthbook.io/api/Holdouts/operation/updateHoldout openapi.yaml PUT /v1/holdouts/{id} Updates a Holdout. Use the start, start-analysis, and stop endpoints to move it through its lifecycle. # Information Schema Table Source: https://docs.growthbook.io/api/InformationSchemaTable_model # Information Schema Source: https://docs.growthbook.io/api/InformationSchema_model # Create a single interleavingExposureQuery Source: https://docs.growthbook.io/api/InterleavingExposureQueries/operation/createInterleavingExposureQuery openapi.yaml POST /v1/interleaving-exposure-queries # Delete a single interleavingExposureQuery Source: https://docs.growthbook.io/api/InterleavingExposureQueries/operation/deleteInterleavingExposureQuery openapi.yaml DELETE /v1/interleaving-exposure-queries/{id} # Get a single interleavingExposureQuery Source: https://docs.growthbook.io/api/InterleavingExposureQueries/operation/getInterleavingExposureQuery openapi.yaml GET /v1/interleaving-exposure-queries/{id} # Get all interleavingExposureQueries Source: https://docs.growthbook.io/api/InterleavingExposureQueries/operation/listInterleavingExposureQueries openapi.yaml GET /v1/interleaving-exposure-queries # Update a single interleavingExposureQuery Source: https://docs.growthbook.io/api/InterleavingExposureQueries/operation/updateInterleavingExposureQuery openapi.yaml PUT /v1/interleaving-exposure-queries/{id} # Interleaving Exposure Query Source: https://docs.growthbook.io/api/InterleavingExposureQuery_model # Interleaving Snapshot Status Source: https://docs.growthbook.io/api/InterleavingSnapshotStatus_model # Interleaving Snapshot Source: https://docs.growthbook.io/api/InterleavingSnapshot_model # Interleaving Source: https://docs.growthbook.io/api/Interleaving_model # Create a single interleaving Source: https://docs.growthbook.io/api/Interleavings/operation/createInterleaving openapi.yaml POST /v1/interleavings # Delete a single interleaving Source: https://docs.growthbook.io/api/Interleavings/operation/deleteInterleaving openapi.yaml DELETE /v1/interleavings/{id} Permanently deletes an interleaving experiment and its results snapshots. Archive instead to keep the results. # Get a single interleaving Source: https://docs.growthbook.io/api/Interleavings/operation/getInterleaving openapi.yaml GET /v1/interleavings/{id} # Get the latest interleaving experiment results Source: https://docs.growthbook.io/api/Interleavings/operation/getInterleavingResults openapi.yaml GET /v1/interleavings/{id}/results Returns the most recent successful `snapshot` and the status-only `latest` run. Poll `latest` while a refresh is in progress; `snapshot` remains null until a run succeeds. # Get an interleaving experiment results snapshot Source: https://docs.growthbook.io/api/Interleavings/operation/getInterleavingSnapshot openapi.yaml GET /v1/interleavings/{id}/snapshots/{snapshotId} # Get interleaving experiment run status Source: https://docs.growthbook.io/api/Interleavings/operation/getInterleavingStatus openapi.yaml GET /v1/interleavings/{id}/status Just the status of the most recent results run (id, status, queries, timestamps). A lightweight alternative to the full results endpoint, intended for polling while a run is in progress. # List interleaving experiment results snapshots Source: https://docs.growthbook.io/api/Interleavings/operation/listInterleavingSnapshots openapi.yaml GET /v1/interleavings/{id}/snapshots Newest snapshot first, regardless of status. # Get all interleavings Source: https://docs.growthbook.io/api/Interleavings/operation/listInterleavings openapi.yaml GET /v1/interleavings # Refresh interleaving experiment results Source: https://docs.growthbook.io/api/Interleavings/operation/refreshInterleaving openapi.yaml POST /v1/interleavings/{id}/refresh Creates a results snapshot and starts its metric queries against the Data Source. The experiment must have been started and have at least one metric. Queries run asynchronously; poll the snapshot until its `status` is `success` or `error`. # Start an interleaving experiment Source: https://docs.growthbook.io/api/Interleavings/operation/startInterleaving openapi.yaml POST /v1/interleavings/{id}/start Starts serving the interleaving to SDKs in its enabled environments. A stopped interleaving can be started again; its start date is kept. # Stop an interleaving experiment Source: https://docs.growthbook.io/api/Interleavings/operation/stopInterleaving openapi.yaml POST /v1/interleavings/{id}/stop Removes the interleaving from SDK payloads, so `interleave()` falls back to `fallbackConfig`. # Update a single interleaving Source: https://docs.growthbook.io/api/Interleavings/operation/updateInterleaving openapi.yaml PUT /v1/interleavings/{id} Archive an interleaving experiment by setting `archived`. Use the start and stop endpoints to change its status. # Learning Source: https://docs.growthbook.io/api/Learning_model # Create a single learning Source: https://docs.growthbook.io/api/Learnings/operation/createLearning openapi.yaml POST /v1/learnings # Delete a single learning Source: https://docs.growthbook.io/api/Learnings/operation/deleteLearning openapi.yaml DELETE /v1/learnings/{id} # Get a single learning Source: https://docs.growthbook.io/api/Learnings/operation/getLearning openapi.yaml GET /v1/learnings/{id} # Get all learnings Source: https://docs.growthbook.io/api/Learnings/operation/listLearnings openapi.yaml GET /v1/learnings # Search saved Learnings by meaning rather than keyword Source: https://docs.growthbook.io/api/Learnings/operation/searchLearnings openapi.yaml POST /v1/learnings/search # Update a single learning Source: https://docs.growthbook.io/api/Learnings/operation/updateLearning openapi.yaml PUT /v1/learnings/{id} # Lookback Override Source: https://docs.growthbook.io/api/LookbackOverride_model # Member Source: https://docs.growthbook.io/api/Member_model # Metric Analysis Source: https://docs.growthbook.io/api/MetricAnalysis_model # Metric Group Source: https://docs.growthbook.io/api/MetricGroup_model # Create a single metricGroup Source: https://docs.growthbook.io/api/MetricGroups/operation/createMetricGroup openapi.yaml POST /v1/metric-groups # Delete a single metricGroup Source: https://docs.growthbook.io/api/MetricGroups/operation/deleteMetricGroup openapi.yaml DELETE /v1/metric-groups/{id} # Get a single metricGroup Source: https://docs.growthbook.io/api/MetricGroups/operation/getMetricGroup openapi.yaml GET /v1/metric-groups/{id} # Get all metricGroups Source: https://docs.growthbook.io/api/MetricGroups/operation/listMetricGroups openapi.yaml GET /v1/metric-groups # Update a single metricGroup Source: https://docs.growthbook.io/api/MetricGroups/operation/updateMetricGroup openapi.yaml PUT /v1/metric-groups/{id} # Metric Usage Source: https://docs.growthbook.io/api/MetricUsage_model # Metric Source: https://docs.growthbook.io/api/Metric_model # Namespace Experiment Member Source: https://docs.growthbook.io/api/NamespaceExperimentMember_model # Namespace Source: https://docs.growthbook.io/api/Namespace_model # Organization Source: https://docs.growthbook.io/api/Organization_model # Pagination Fields Source: https://docs.growthbook.io/api/PaginationFields_model # Population Source: https://docs.growthbook.io/api/Population_model # Create a single population Source: https://docs.growthbook.io/api/Populations/operation/createPopulation openapi.yaml POST /v1/populations # Delete a single population Source: https://docs.growthbook.io/api/Populations/operation/deletePopulation openapi.yaml DELETE /v1/populations/{id} # Get a single population Source: https://docs.growthbook.io/api/Populations/operation/getPopulation openapi.yaml GET /v1/populations/{id} # Get all populations Source: https://docs.growthbook.io/api/Populations/operation/listPopulations openapi.yaml GET /v1/populations # Update a single population Source: https://docs.growthbook.io/api/Populations/operation/updatePopulation openapi.yaml PUT /v1/populations/{id} # Project Source: https://docs.growthbook.io/api/Project_model # Query Source: https://docs.growthbook.io/api/Query_model # Ramp Schedule Template Source: https://docs.growthbook.io/api/RampScheduleTemplate_model # Create a single rampScheduleTemplate Source: https://docs.growthbook.io/api/RampScheduleTemplates/operation/createRampScheduleTemplate openapi.yaml POST /v1/ramp-schedule-templates # Delete a single rampScheduleTemplate Source: https://docs.growthbook.io/api/RampScheduleTemplates/operation/deleteRampScheduleTemplate openapi.yaml DELETE /v1/ramp-schedule-templates/{id} # Get a single rampScheduleTemplate Source: https://docs.growthbook.io/api/RampScheduleTemplates/operation/getRampScheduleTemplate openapi.yaml GET /v1/ramp-schedule-templates/{id} # Get all rampScheduleTemplates Source: https://docs.growthbook.io/api/RampScheduleTemplates/operation/listRampScheduleTemplates openapi.yaml GET /v1/ramp-schedule-templates # Update a single rampScheduleTemplate Source: https://docs.growthbook.io/api/RampScheduleTemplates/operation/updateRampScheduleTemplate openapi.yaml PUT /v1/ramp-schedule-templates/{id} # Ramp Schedule Source: https://docs.growthbook.io/api/RampSchedule_model # Report Source: https://docs.growthbook.io/api/Report_model # Require Review Rule Input Source: https://docs.growthbook.io/api/RequireReviewRuleInput_model # Require Review Rule Source: https://docs.growthbook.io/api/RequireReviewRule_model # Revision Id Ref Source: https://docs.growthbook.io/api/RevisionIdRef_model # Safe Rollout Rule Source: https://docs.growthbook.io/api/Safe Rollout Rule_model # Saved Group Approval Rule Source: https://docs.growthbook.io/api/SavedGroupApprovalRule_model # Saved Group Reference Resource Source: https://docs.growthbook.io/api/SavedGroupReferenceResource_model # Saved Group References Source: https://docs.growthbook.io/api/SavedGroupReferences_model # Saved Group Revision Activity Log Entry Source: https://docs.growthbook.io/api/SavedGroupRevisionActivityLogEntry_model # Saved Group Revision Ref Source: https://docs.growthbook.io/api/SavedGroupRevisionRef_model # Saved Group Revision Review Source: https://docs.growthbook.io/api/SavedGroupRevisionReview_model # Saved Group Revision Source: https://docs.growthbook.io/api/SavedGroupRevision_model # Saved Group Upload Source: https://docs.growthbook.io/api/SavedGroupUpload_model # Saved Group Source: https://docs.growthbook.io/api/SavedGroup_model # Schedule Rule Source: https://docs.growthbook.io/api/ScheduleRule_model # Scheduled Stop Plan Source: https://docs.growthbook.io/api/ScheduledStopPlan_model # Sdk Connection Source: https://docs.growthbook.io/api/SdkConnection_model # Segment Source: https://docs.growthbook.io/api/Segment_model # Settings Source: https://docs.growthbook.io/api/Settings_model # Targeting Rule Source: https://docs.growthbook.io/api/Targeting Rule_model # Targeting Review Rule Source: https://docs.growthbook.io/api/TargetingReviewRule_model # Team Source: https://docs.growthbook.io/api/Team_model # Add members to team Source: https://docs.growthbook.io/api/Teams/operation/addTeamMembers openapi.yaml POST /v1/teams/{id}/members # Create a single team Source: https://docs.growthbook.io/api/Teams/operation/createTeam openapi.yaml POST /v1/teams # Delete a single team Source: https://docs.growthbook.io/api/Teams/operation/deleteTeam openapi.yaml DELETE /v1/teams/{id} # Get a single team Source: https://docs.growthbook.io/api/Teams/operation/getTeam openapi.yaml GET /v1/teams/{id} # Get all teams Source: https://docs.growthbook.io/api/Teams/operation/listTeams openapi.yaml GET /v1/teams # Remove members from team Source: https://docs.growthbook.io/api/Teams/operation/removeTeamMember openapi.yaml DELETE /v1/teams/{id}/members # Update a single team Source: https://docs.growthbook.io/api/Teams/operation/updateTeam openapi.yaml PUT /v1/teams/{id} # Visual Change Source: https://docs.growthbook.io/api/VisualChange_model # Visual Changeset Source: https://docs.growthbook.io/api/VisualChangeset_model # Visual Editor Cb Experiment Stub Source: https://docs.growthbook.io/api/VisualEditorCbExperimentStub_model # Deletes a single archetype Source: https://docs.growthbook.io/api/archetypes/operation/deleteArchetype openapi.yaml DELETE /v1/archetypes/{id} # Get a single archetype Source: https://docs.growthbook.io/api/archetypes/operation/getArchetype openapi.yaml GET /v1/archetypes/{id} # Get the organization's archetypes Source: https://docs.growthbook.io/api/archetypes/operation/listArchetypes openapi.yaml GET /v1/archetypes # Create a single archetype Source: https://docs.growthbook.io/api/archetypes/operation/postArchetype openapi.yaml POST /v1/archetypes # Update a single archetype Source: https://docs.growthbook.io/api/archetypes/operation/putArchetype openapi.yaml PUT /v1/archetypes/{id} # Deletes a single attribute Source: https://docs.growthbook.io/api/attributes/operation/deleteAttribute openapi.yaml DELETE /v1/attributes/{property} # Get the organization's attributes Source: https://docs.growthbook.io/api/attributes/operation/listAttributes openapi.yaml GET /v1/attributes # Create a new attribute Source: https://docs.growthbook.io/api/attributes/operation/postAttribute openapi.yaml POST /v1/attributes # Update an attribute Source: https://docs.growthbook.io/api/attributes/operation/putAttribute openapi.yaml PUT /v1/attributes/{property} # Get list of code references for a single feature id Source: https://docs.growthbook.io/api/code-references/operation/getCodeRefs openapi.yaml GET /v1/code-refs/{id} # Get list of all code references for the current organization Source: https://docs.growthbook.io/api/code-references/operation/listCodeRefs openapi.yaml GET /v1/code-refs # Submit list of code references Source: https://docs.growthbook.io/api/code-references/operation/postCodeRefs openapi.yaml POST /v1/code-refs # Remove a config's per-source render projection on a draft Source: https://docs.growthbook.io/api/config-revisions/operation/deleteConfigRevisionProjection openapi.yaml DELETE /v1/configs-revisions/{key}/{version}/projection Stages removal of the `source` projection from `renderProjections` on the draft (the canonical schema is unchanged). Published through the normal flow. Pass `version: "new"` to auto-create a draft. # Remove one property from a config draft revision's value Source: https://docs.growthbook.io/api/config-revisions/operation/deleteConfigRevisionProperty openapi.yaml DELETE /v1/configs-revisions/{key}/{version}/property Stages removal of a single property from this config's own value on the draft; the config then inherits that property from its parent (if any). Every other property is untouched. Pass `version: "new"` to auto-create a draft. # Get a single config revision Source: https://docs.growthbook.io/api/config-revisions/operation/getConfigRevision openapi.yaml GET /v1/configs-revisions/{key}/{version} Returns the revision at the specified version for this config. Use `GET /configs-revisions/{key}/latest` for the most recent active draft. # Get the most recent active draft revision Source: https://docs.growthbook.io/api/config-revisions/operation/getConfigRevisionLatest openapi.yaml GET /v1/configs-revisions/{key}/latest Returns the most recently updated open (non-merged, non-discarded) revision for the config. Returns 404 if there is no active draft. Pass `mine=true` to restrict to drafts authored by the calling user. Needs a personal access token, or an organization API key that assumes a named member's role. # Get merge status for a draft revision Source: https://docs.growthbook.io/api/config-revisions/operation/getConfigRevisionMergeStatus openapi.yaml GET /v1/configs-revisions/{key}/{version}/merge-status Runs a dry-run merge of the draft against the current live config and returns any conflicts. Use this before publishing to preview changes and detect conflicting edits. # List revisions for a config Source: https://docs.growthbook.io/api/config-revisions/operation/getConfigRevisions openapi.yaml GET /v1/configs-revisions/{key} Returns a paginated list of revisions for this config, sorted newest-first. Optionally filtered by status, author, or the calling user's involvement. # List config revisions across the organization Source: https://docs.growthbook.io/api/config-revisions/operation/listConfigRevisions openapi.yaml GET /v1/configs-revisions Returns a paginated list of revisions across all configs in the organization, sorted newest-first. Optionally filtered by config, status, author, or the calling user's involvement. # Create a draft revision Source: https://docs.growthbook.io/api/config-revisions/operation/postConfigRevision openapi.yaml POST /v1/configs-revisions/{key} Creates a new draft revision branched from the current live config. A config can have multiple concurrent drafts; use this to start an isolated line of edits. # Discard a draft revision Source: https://docs.growthbook.io/api/config-revisions/operation/postConfigRevisionDiscard openapi.yaml POST /v1/configs-revisions/{key}/{version}/discard Permanently discards a draft revision. Only open revisions (not merged or already-discarded) can be discarded. # Publish a draft revision Source: https://docs.growthbook.io/api/config-revisions/operation/postConfigRevisionPublish openapi.yaml POST /v1/configs-revisions/{key}/{version}/publish Publishes the draft and makes its changes live. The caller needs Publish access for the affected environments. When approval is required, the draft must be approved unless the caller has Bypass draft approvals access. If the organization requires rebasing, an out-of-date draft must be rebased first; an authorized caller can instead send `ignoreWarnings: true` to force-publish it. A locked Config cannot be published. A 422 response lists every blocking gate and the available resolution. Publishing a schema change also normalizes descendant Configs according to their inheritance rules. # Rebase a draft revision onto the current live config Source: https://docs.growthbook.io/api/config-revisions/operation/postConfigRevisionRebase openapi.yaml POST /v1/configs-revisions/{key}/{version}/rebase Updates the draft's base snapshot to the current live state, applying the draft's changes on top. Supply `conflictResolutions` to resolve any conflicting fields. Strategies are `overwrite` (use the draft's value), `discard` (keep the live value), or `union` (merge arrays without duplicates — for array fields like `extends`; pass a `customValues` entry to supply the resolved array yourself). # Recall a review request Source: https://docs.growthbook.io/api/config-revisions/operation/postConfigRevisionRecallReview openapi.yaml POST /v1/configs-revisions/{key}/{version}/recall-review Pulls a revision in review (`pending-review`, `changes-requested`, or `approved`) back to `draft`, clearing existing reviews and disarming any auto-publish-on-approval. # Reopen a discarded revision Source: https://docs.growthbook.io/api/config-revisions/operation/postConfigRevisionReopen openapi.yaml POST /v1/configs-revisions/{key}/{version}/reopen Returns a previously discarded revision to `draft` status so it can be edited and published again. Only discarded revisions can be reopened. # Request review for a draft revision Source: https://docs.growthbook.io/api/config-revisions/operation/postConfigRevisionRequestReview openapi.yaml POST /v1/configs-revisions/{key}/{version}/request-review Moves the draft from `draft` into `pending-review`. Notifies reviewers per the org's approval-flow settings. Set `autoPublishOnApproval` to `true` to publish the revision automatically the moment it is approved. This requires the org to have auto-publish-on-approval enabled and the caller to have publish permission on the config. # Revert the config to a prior revision Source: https://docs.growthbook.io/api/config-revisions/operation/postConfigRevisionRevert openapi.yaml POST /v1/configs-revisions/{key}/{version}/revert Creates a new draft (or immediately publishes) whose content matches the specified historical revision. Defaults to creating a draft; when the org enables 'reverts bypass approval' it defaults to publishing immediately. Pass `strategy` to override. # Schedule (or cancel) a deferred publish Source: https://docs.growthbook.io/api/config-revisions/operation/postConfigRevisionSchedulePublish openapi.yaml POST /v1/configs-revisions/{key}/{version}/schedule-publish Arms a revision to publish automatically at a future time. Pass `scheduledPublishAt` as an RFC3339 timestamp in the future to arm, or `null` to cancel a pending schedule. Requires the `scheduled-revisions` commercial feature and publish permission on the config. A draft that still requires approval must request review first (or be armed with `bypassApproval` by a caller who can bypass). # Submit a review on a draft revision Source: https://docs.growthbook.io/api/config-revisions/operation/postConfigRevisionSubmitReview openapi.yaml POST /v1/configs-revisions/{key}/{version}/submit-review Submits an `approve`, `request-changes`, or `comment` review on the revision. The verdict belongs to whoever the request acts as; see [Naming who asked](/api/introduction#naming-who-asked). No one can rule on a draft they created. Submitting `approve` or `request-changes` needs Review access. A `comment` is participation rather than a verdict, so it is also open to the Comments permission or draft authority on the entity. Authors and contributors cannot submit `approve` reviews on their own drafts when the org has `blockSelfApproval` enabled. When `decision` is `approve` and the revision has `autoPublishOnApproval` enabled, the revision is automatically published after approval. The response includes `autoPublished: true` when this happens. Pass `skipAutoPublish: true` to approve without triggering auto-publish. # Retract your own review verdict Source: https://docs.growthbook.io/api/config-revisions/operation/postConfigRevisionUndoReview openapi.yaml POST /v1/configs-revisions/{key}/{version}/undo-review Retracts the calling user's own active `approve` or `request-changes` verdict, returning the revision to `pending-review`. Review comments stay in the log. Retracting a `request-changes` can leave the revision approved by someone else, in which case an armed auto-publish fires. The verdict belongs to whoever the request acts as; see [Naming who asked](/api/introduction#naming-who-asked). No one can rule on a draft they created. # Stage an archive/unarchive in a draft revision Source: https://docs.growthbook.io/api/config-revisions/operation/putConfigRevisionArchive openapi.yaml PUT /v1/configs-revisions/{key}/{version}/archive Stages an archive or unarchive on the draft. Pass `version: "new"` to auto-create a draft. Archived configs can be permanently deleted via `DELETE /configs/{key}` once the archive is published. # Update config metadata in a draft revision Source: https://docs.growthbook.io/api/config-revisions/operation/putConfigRevisionMetadata openapi.yaml PUT /v1/configs-revisions/{key}/{version}/metadata Stages metadata changes (name, owner, description, project, lineage parent, extensibility) on the draft. Pass `version: "new"` to auto-create a draft. The change is only applied to the live config when the revision is merged. # Set (or update) a config's per-source render projection on a draft Source: https://docs.growthbook.io/api/config-revisions/operation/putConfigRevisionProjection openapi.yaml PUT /v1/configs-revisions/{key}/{version}/projection Stages a per-source render projection on the draft, AND the schema it implies. Provide a named `schema` source (`{ type: "typescript" | "protobuf" | "python" | "go" | "rust" | "json-schema", value }`) for the consuming codebase identified by `source`: GrowthBook derives the config's canonical schema from it (so the change projects into the Config) and captures that source's named-type structure under `renderProjections[source]`. Both are staged on the draft and published through the normal flow. Pass `version: "new"` to auto-create a draft. Lossy conversions degrade with `warnings`. # Set one property of a config draft revision's value Source: https://docs.growthbook.io/api/config-revisions/operation/putConfigRevisionProperty openapi.yaml PUT /v1/configs-revisions/{key}/{version}/property Stages a single property of this config's own value on the draft, leaving every other property untouched. Prefer this over `PUT .../value` when changing one field: a whole-value write from a stale read silently drops properties someone else added in the meantime. `null` is a value (it does not remove the property) — use `DELETE .../property` to remove one. Pass `version: "new"` to auto-create a draft. # Update or import the schema of a config draft revision Source: https://docs.growthbook.io/api/config-revisions/operation/putConfigRevisionSchema openapi.yaml PUT /v1/configs-revisions/{key}/{version}/schema Stages this config's field schema on the draft. Provide exactly ONE source: - `schema`: a schema document — `{ type: "json-schema", value }` (a JSON Schema object) or `{ type: "typescript", value }` (TypeScript source). **JSON Schema is the recommended ("happy path") format** — it is the canonical pivot, preserves nested objects/arrays, and resolves local `$ref`/`$defs` (so generator output with referenced types works). `typescript` is a best-effort convenience parser. All conversions are lossy-by-design and degrade exotic constructs to permissive types WITH warnings (returned in `warnings`). - `infer: true`: derive the schema from the draft's value. Fields whose key a published ancestor already owns follow "base wins": an identical re-declaration is stripped with a `redundant-declaration` warning; one with a differing definition is rejected. Pass `version: "new"` to auto-create a draft. # Update the value of a config draft revision Source: https://docs.growthbook.io/api/config-revisions/operation/putConfigRevisionValue openapi.yaml PUT /v1/configs-revisions/{key}/{version}/value Stages a new `value` (this config's own JSON object) on the draft. Pass `version: "new"` to auto-create a draft. A `@config:` inheritance entry in the value is rejected — express lineage via the `parent`/`extends` metadata fields instead. Configs are environment-agnostic: there is no per-environment override (use a Constant for that). Inheritance is a deep (targeted) patch: this value is merged onto the resolved parent recursively, key by key — restate only the leaves you want to change and the rest are inherited. Arrays and scalars replace wholesale, `null` is a value (it does not delete a key), and a value composed from a constant via `$extends` is applied whole. Set `inferSchemaIfMissing: true` to derive and stage a field schema from the value when the config has none yet. # Archive a single config Source: https://docs.growthbook.io/api/configs/operation/archiveConfig openapi.yaml POST /v1/configs/{key}/archive Archives a config. A child config (including an environment/project override) is archived outright when its live value is an empty patch or nothing serves it. When archiving would strip a value that live features or other configs still consume, the request returns a 422 listing the blocking gates — re-submit with `"ignoreWarnings": true` in the request body to acknowledge and proceed. A locked config, or one whose org requires approval, returns its own gate (unlock or route the change through a draft revision). # Delete a single config Source: https://docs.growthbook.io/api/configs/operation/deleteConfig openapi.yaml DELETE /v1/configs/{key} # Get a single config Source: https://docs.growthbook.io/api/configs/operation/getConfig openapi.yaml GET /v1/configs/{key} # Get the feature rules and default values implementing each key Source: https://docs.growthbook.io/api/configs/operation/getConfigKeyUsage openapi.yaml GET /v1/configs/{key}/key-usage Lists every feature rule and default value that overrides a key of this config's lineage family, so you can see which keys are implemented and where. # Get the full lineage (family tree) for a config Source: https://docs.growthbook.io/api/configs/operation/getConfigLineage openapi.yaml GET /v1/configs/{key}/lineage # Get features and configs that reference this config Source: https://docs.growthbook.io/api/configs/operation/getConfigReferences openapi.yaml GET /v1/configs/{key}/references # Export a config's schema Source: https://docs.growthbook.io/api/configs/operation/getConfigSchema openapi.yaml GET /v1/configs/{key}/schema # Get all configs Source: https://docs.growthbook.io/api/configs/operation/listConfigs openapi.yaml GET /v1/configs # Lock a config at its current published revision Source: https://docs.growthbook.io/api/configs/operation/lockConfig openapi.yaml POST /v1/configs/{key}/lock Locks the Config to its current published revision. Drafts can still be created and edited, but direct updates, publishes, scheduled publishes, reverts, and archives are blocked. The response returns the pinned revision in `lockedRevision`. Unlocking requires Bypass draft approvals access. # Create a single config Source: https://docs.growthbook.io/api/configs/operation/postConfig openapi.yaml POST /v1/configs # Unarchive a single config Source: https://docs.growthbook.io/api/configs/operation/unarchiveConfig openapi.yaml POST /v1/configs/{key}/unarchive # Unlock a config Source: https://docs.growthbook.io/api/configs/operation/unlockConfig openapi.yaml POST /v1/configs/{key}/unlock Removes the Config lock so changes can be published again. The caller must have Bypass draft approvals access in the Config's Project. # Partially update a single config Source: https://docs.growthbook.io/api/configs/operation/updateConfig openapi.yaml POST /v1/configs/{key} Applies the change immediately and records it as a published revision, so it appears in history and fires revision webhooks. When the organization requires approvals, open a draft instead or pass `bypassApproval` with the bypass permission. # Verify a config's schema against a source (drift check) Source: https://docs.growthbook.io/api/configs/operation/verifyConfigSchema openapi.yaml POST /v1/configs/{key}/schema/verify # Get a single constant revision Source: https://docs.growthbook.io/api/constant-revisions/operation/getConstantRevision openapi.yaml GET /v1/constants-revisions/{key}/{version} Returns the revision at the specified version for this constant. Use `GET /constants-revisions/{key}/latest` for the most recent active draft. # Get the most recent active draft revision Source: https://docs.growthbook.io/api/constant-revisions/operation/getConstantRevisionLatest openapi.yaml GET /v1/constants-revisions/{key}/latest Returns the most recently updated open (non-merged, non-discarded) revision for the constant. Returns 404 if there is no active draft. Pass `mine=true` to restrict to drafts authored by the calling user. Needs a personal access token, or an organization API key that assumes a named member's role. # Get merge status for a draft revision Source: https://docs.growthbook.io/api/constant-revisions/operation/getConstantRevisionMergeStatus openapi.yaml GET /v1/constants-revisions/{key}/{version}/merge-status Runs a dry-run merge of the draft against the current live constant and returns any conflicts. Use this before publishing to preview changes and detect conflicting edits. # List revisions for a constant Source: https://docs.growthbook.io/api/constant-revisions/operation/getConstantRevisions openapi.yaml GET /v1/constants-revisions/{key} Returns a paginated list of revisions for this constant, sorted newest-first. Optionally filtered by status, author, or the calling user's involvement. # List constant revisions across the organization Source: https://docs.growthbook.io/api/constant-revisions/operation/listConstantRevisions openapi.yaml GET /v1/constants-revisions Returns a paginated list of revisions across all constants in the organization, sorted newest-first. Optionally filtered by constant, status, author, or the calling user's involvement. # Create a draft revision Source: https://docs.growthbook.io/api/constant-revisions/operation/postConstantRevision openapi.yaml POST /v1/constants-revisions/{key} Creates a new draft revision branched from the current live constant. A constant can have multiple concurrent drafts; use this to start an isolated line of edits. # Discard a draft revision Source: https://docs.growthbook.io/api/constant-revisions/operation/postConstantRevisionDiscard openapi.yaml POST /v1/constants-revisions/{key}/{version}/discard Permanently discards a draft revision. Only open revisions (not merged or already-discarded) can be discarded. # Publish a draft revision Source: https://docs.growthbook.io/api/constant-revisions/operation/postConstantRevisionPublish openapi.yaml POST /v1/constants-revisions/{key}/{version}/publish Publishes the draft and makes its changes live. The caller needs Publish access for the affected environments. When approval is required, the draft must be approved unless the caller has Bypass draft approvals access. If the organization requires rebasing, an out-of-date draft must be rebased first; an authorized caller can instead send `ignoreWarnings: true` to force-publish it. A 422 response lists every blocking gate and the available resolution. # Rebase a draft revision onto the current live constant Source: https://docs.growthbook.io/api/constant-revisions/operation/postConstantRevisionRebase openapi.yaml POST /v1/constants-revisions/{key}/{version}/rebase Updates the draft's base snapshot to the current live state, applying the draft's changes on top. Supply `conflictResolutions` to resolve any conflicting fields. Strategies are `overwrite` (use the draft's value) or `discard` (keep the live value). # Recall a review request Source: https://docs.growthbook.io/api/constant-revisions/operation/postConstantRevisionRecallReview openapi.yaml POST /v1/constants-revisions/{key}/{version}/recall-review Pulls a revision in review (`pending-review`, `changes-requested`, or `approved`) back to `draft`, clearing existing reviews and disarming any auto-publish-on-approval. # Reopen a discarded revision Source: https://docs.growthbook.io/api/constant-revisions/operation/postConstantRevisionReopen openapi.yaml POST /v1/constants-revisions/{key}/{version}/reopen Returns a previously discarded revision to `draft` status so it can be edited and published again. Only discarded revisions can be reopened. # Request review for a draft revision Source: https://docs.growthbook.io/api/constant-revisions/operation/postConstantRevisionRequestReview openapi.yaml POST /v1/constants-revisions/{key}/{version}/request-review Moves the draft from `draft` into `pending-review`. Notifies reviewers per the org's approval-flow settings. Set `autoPublishOnApproval` to `true` to publish the revision automatically the moment it is approved. This requires the org to have auto-publish-on-approval enabled and the caller to have publish permission on the constant. # Revert the constant to a prior revision Source: https://docs.growthbook.io/api/constant-revisions/operation/postConstantRevisionRevert openapi.yaml POST /v1/constants-revisions/{key}/{version}/revert Creates a new draft (or immediately publishes) whose content matches the specified historical revision. Defaults to creating a draft; when the org enables 'reverts bypass approval' it defaults to publishing immediately. Pass `strategy` to override. # Schedule (or cancel) a deferred publish Source: https://docs.growthbook.io/api/constant-revisions/operation/postConstantRevisionSchedulePublish openapi.yaml POST /v1/constants-revisions/{key}/{version}/schedule-publish Arms a revision to publish automatically at a future time. Pass `scheduledPublishAt` as an RFC3339 timestamp in the future to arm, or `null` to cancel a pending schedule. Requires the `scheduled-revisions` commercial feature and publish permission on the Constant. A draft that still requires approval must request review first (or be armed with `bypassApproval` by a caller who can bypass). # Submit a review on a draft revision Source: https://docs.growthbook.io/api/constant-revisions/operation/postConstantRevisionSubmitReview openapi.yaml POST /v1/constants-revisions/{key}/{version}/submit-review Submits an `approve`, `request-changes`, or `comment` review on the revision. The verdict belongs to whoever the request acts as; see [Naming who asked](/api/introduction#naming-who-asked). No one can rule on a draft they created. Submitting `approve` or `request-changes` needs Review access. A `comment` is participation rather than a verdict, so it is also open to the Comments permission or draft authority on the entity. Authors and contributors cannot submit `approve` reviews on their own drafts when the org has `blockSelfApproval` enabled. When `decision` is `approve` and the revision has `autoPublishOnApproval` enabled, the revision is automatically published after approval. The response includes `autoPublished: true` when this happens. Pass `skipAutoPublish: true` to approve without triggering auto-publish. # Retract your own review verdict Source: https://docs.growthbook.io/api/constant-revisions/operation/postConstantRevisionUndoReview openapi.yaml POST /v1/constants-revisions/{key}/{version}/undo-review Retracts the calling user's own active `approve` or `request-changes` verdict, returning the revision to `pending-review`. Review comments stay in the log. Retracting a `request-changes` can leave the revision approved by someone else, in which case an armed auto-publish fires. The verdict belongs to whoever the request acts as; see [Naming who asked](/api/introduction#naming-who-asked). No one can rule on a draft they created. # Stage an archive/unarchive in a draft revision Source: https://docs.growthbook.io/api/constant-revisions/operation/putConstantRevisionArchive openapi.yaml PUT /v1/constants-revisions/{key}/{version}/archive Stages an archive or unarchive on the draft. Pass `version: "new"` to auto-create a draft. Archived constants can be permanently deleted via `DELETE /constants/{key}` once the archive is published. # Update constant metadata in a draft revision Source: https://docs.growthbook.io/api/constant-revisions/operation/putConstantRevisionMetadata openapi.yaml PUT /v1/constants-revisions/{key}/{version}/metadata Stages metadata changes (name, owner, description, project) on the draft. Pass `version: "new"` to auto-create a draft. The change is only applied to the live constant when the revision is merged. # Update the value of a constant draft revision Source: https://docs.growthbook.io/api/constant-revisions/operation/putConstantRevisionValue openapi.yaml PUT /v1/constants-revisions/{key}/{version}/value Stages a new default `value` and/or per-environment `environmentValues` on the draft. At least one must be supplied. Pass `version: "new"` to auto-create a draft. The value must match the constant's type (valid JSON for `json` constants). # Archive a single constant Source: https://docs.growthbook.io/api/constants/operation/archiveConstant openapi.yaml POST /v1/constants/{key}/archive # Delete a single constant Source: https://docs.growthbook.io/api/constants/operation/deleteConstant openapi.yaml DELETE /v1/constants/{key} # Get a single constant Source: https://docs.growthbook.io/api/constants/operation/getConstant openapi.yaml GET /v1/constants/{key} # Get features and constants that reference this constant Source: https://docs.growthbook.io/api/constants/operation/getConstantReferences openapi.yaml GET /v1/constants/{key}/references # Get all constants Source: https://docs.growthbook.io/api/constants/operation/listConstants openapi.yaml GET /v1/constants # Create a single constant Source: https://docs.growthbook.io/api/constants/operation/postConstant openapi.yaml POST /v1/constants # Unarchive a single constant Source: https://docs.growthbook.io/api/constants/operation/unarchiveConstant openapi.yaml POST /v1/constants/{key}/unarchive # Partially update a single constant Source: https://docs.growthbook.io/api/constants/operation/updateConstant openapi.yaml POST /v1/constants/{key} Applies the change immediately and records it as a published revision, so it appears in history and fires revision webhooks. When the organization requires approvals, open a draft instead or pass `bypassApproval` with the bypass permission. # Delete a single custom hook Source: https://docs.growthbook.io/api/custom-hooks/operation/deleteCustomHook openapi.yaml DELETE /v1/custom-hooks/{id} # Get a single custom hook Source: https://docs.growthbook.io/api/custom-hooks/operation/getCustomHook openapi.yaml GET /v1/custom-hooks/{id} # List a custom hook's version history Source: https://docs.growthbook.io/api/custom-hooks/operation/listCustomHookHistory openapi.yaml GET /v1/custom-hooks/{id}/history # Get all custom hooks Source: https://docs.growthbook.io/api/custom-hooks/operation/listCustomHooks openapi.yaml GET /v1/custom-hooks # Create a single custom hook Source: https://docs.growthbook.io/api/custom-hooks/operation/postCustomHook openapi.yaml POST /v1/custom-hooks # Revert a custom hook to a previous version Source: https://docs.growthbook.io/api/custom-hooks/operation/revertCustomHook openapi.yaml POST /v1/custom-hooks/{id}/revert # Dry-run hook code in the sandbox Source: https://docs.growthbook.io/api/custom-hooks/operation/testCustomHook openapi.yaml POST /v1/custom-hooks/test # Partially update a single custom hook Source: https://docs.growthbook.io/api/custom-hooks/operation/updateCustomHook openapi.yaml POST /v1/custom-hooks/{id} # Get a single data source Source: https://docs.growthbook.io/api/data-sources/operation/getDataSource openapi.yaml GET /v1/data-sources/{id} # Get a Data Source's Information Schema Source: https://docs.growthbook.io/api/data-sources/operation/getInformationSchema openapi.yaml GET /v1/data-sources/{dataSourceId}/information-schema Returns cached database schema metadata for a data source, including databases, schemas, and tables. The information schema is automatically created when a SQL-based data source is added. Not all data source types support information schemas. # Get a single Information Schema Table by id Source: https://docs.growthbook.io/api/data-sources/operation/getInformationSchemaTable openapi.yaml GET /v1/information-schema-tables/{tableId} Returns cached metadata for a specific table in the Data Source, including columns and their data types. Not all data source types support information schemas. # Get warehouse table schemas Source: https://docs.growthbook.io/api/data-sources/operation/getWarehouseTableSchema openapi.yaml POST /v1/data-sources/{id}/sql/table-schema # Get all data sources Source: https://docs.growthbook.io/api/data-sources/operation/listDataSources openapi.yaml GET /v1/data-sources # Preview distinct column values Source: https://docs.growthbook.io/api/data-sources/operation/previewWarehouseColumnValues openapi.yaml POST /v1/data-sources/{id}/sql/preview-values # Execute a read-only SQL query Source: https://docs.growthbook.io/api/data-sources/operation/runSqlQuery openapi.yaml POST /v1/data-sources/{id}/sql/run-query # Search warehouse tables Source: https://docs.growthbook.io/api/data-sources/operation/searchWarehouseTables openapi.yaml POST /v1/data-sources/{id}/sql/search-tables # Deletes a single dimension Source: https://docs.growthbook.io/api/dimensions/operation/deleteDimension openapi.yaml DELETE /v1/dimensions/{id} # Get a single dimension Source: https://docs.growthbook.io/api/dimensions/operation/getDimension openapi.yaml GET /v1/dimensions/{id} # Get all dimensions Source: https://docs.growthbook.io/api/dimensions/operation/listDimensions openapi.yaml GET /v1/dimensions # Create a single dimension Source: https://docs.growthbook.io/api/dimensions/operation/postDimension openapi.yaml POST /v1/dimensions # Update a single dimension Source: https://docs.growthbook.io/api/dimensions/operation/updateDimension openapi.yaml POST /v1/dimensions/{id} # Deletes a single environment Source: https://docs.growthbook.io/api/environments/operation/deleteEnvironment openapi.yaml DELETE /v1/environments/{id} # Get the organization's environments Source: https://docs.growthbook.io/api/environments/operation/listEnvironments openapi.yaml GET /v1/environments # Create a new environment Source: https://docs.growthbook.io/api/environments/operation/postEnvironment openapi.yaml POST /v1/environments # Update an environment Source: https://docs.growthbook.io/api/environments/operation/putEnvironment openapi.yaml PUT /v1/environments/{id} # Delete an experiment Source: https://docs.growthbook.io/api/experiments/operation/deleteExperiment openapi.yaml DELETE /v1/experiments/{id} Archive first: a live experiment can only be deleted when the organization allows the REST API to bypass approval requirements. Linked Feature Flags lose their experiment rule unless `linkedChanges` is "materialize". # Delete a variation screenshot Source: https://docs.growthbook.io/api/experiments/operation/deleteVariationScreenshot openapi.yaml DELETE /v1/experiments/{id}/variation/{variationId}/screenshot # Get a single experiment Source: https://docs.growthbook.io/api/experiments/operation/getExperiment openapi.yaml GET /v1/experiments/{id} # Get a list of experiments with names and ids Source: https://docs.growthbook.io/api/experiments/operation/getExperimentNames openapi.yaml GET /v1/experiment-names # Get results for an experiment Source: https://docs.growthbook.io/api/experiments/operation/getExperimentResults openapi.yaml GET /v1/experiments/{id}/results # Get an experiment pre-launch checklist status Source: https://docs.growthbook.io/api/experiments/operation/getExperimentStartChecklist openapi.yaml GET /v1/experiments/{id}/start-checklist # Get latest results for many experiments Source: https://docs.growthbook.io/api/experiments/operation/listExperimentResults openapi.yaml GET /v1/experiments/results Returns the latest non-dimension snapshot for each experiment matching the filters. Use this to scan results across a portfolio in one call. Pagination semantics: - `total` is the count of experiments matching the filters. - `count` is the length of the returned `experimentResults` array. - Experiments without a completed snapshot are omitted from `experimentResults`, so `count` may be less than the page slice and a page may legitimately return `count: 0` while `hasMore: true`. - `hasMore` and `nextOffset` advance over experiments matching the filters, not over returned results. Use the per-experiment `GET /experiments/{id}/results` endpoint to inspect specific phases or dimensions. # Get all experiments Source: https://docs.growthbook.io/api/experiments/operation/listExperiments openapi.yaml GET /v1/experiments # Create a single experiment Source: https://docs.growthbook.io/api/experiments/operation/postExperiment openapi.yaml POST /v1/experiments Creates a new experiment. ### Naming for human review Keep `name` short; put reasoning in `hypothesis` and context in `description`. `trackingKey` is sent with exposure events, so keep it short and stable. # Archive an experiment Source: https://docs.growthbook.io/api/experiments/operation/postExperimentArchive openapi.yaml POST /v1/experiments/{id}/archive An archived experiment leaves the SDK payload. Refused while a running experiment or a temporary rollout still serves through linked changes unless `linkedChanges` says what happens to them. # Post a comment on an experiment Source: https://docs.growthbook.io/api/experiments/operation/postExperimentComment openapi.yaml POST /v1/experiments/{id}/comment Adds a new comment to an experiment's discussion thread, credited to the person behind the request. Needs a personal access token, or an organization API key that assumes a named member's role. # Modify temporary rollout status for a stopped experiment Source: https://docs.growthbook.io/api/experiments/operation/postExperimentModifyTemporaryRollout openapi.yaml POST /v1/experiments/{id}/modify-temporary-rollout # Create Experiment Snapshot Source: https://docs.growthbook.io/api/experiments/operation/postExperimentSnapshot openapi.yaml POST /v1/experiments/{id}/snapshot # Start/Stage an experiment Source: https://docs.growthbook.io/api/experiments/operation/postExperimentStart openapi.yaml POST /v1/experiments/{id}/start Starts an experiment or stages it for a future start if a `statusUpdateSchedule` is set on the experiment. Both check the launch checklist that `GET /experiments/{id}/start-checklist` returns, as gates. An incomplete item refuses the start with `checklist_incomplete` unless `skipChecklist` is set; a linked Feature Flag blocker refuses it either way. The refusal lists each blocking gate in `gates`, with the flag or permission that gets past it and a route that resolves it. A linked Feature Flag draft that still needs approval, or a flag locked by a ramp, is bypassed automatically when the caller has Bypass draft approvals access for that flag or the organization enables the REST API approval bypass. A start reports what it got past in `bypassedGates` and the drafts it published in `publishedFeatureDrafts`; if a draft can't publish, for example because it needs a rebase, the start fails with `pending_draft_publish_failed` before any is published. A staged start carries the caller's Bypass draft approvals access to when it runs, for the drafts as they were when it was staged: if one has changed since, or the caller has lost that access, it needs approval like any other. The organization's REST API approval bypass doesn't carry to a staged start. # Mark manual pre-launch checklist items complete Source: https://docs.growthbook.io/api/experiments/operation/postExperimentStartChecklistManualComplete openapi.yaml POST /v1/experiments/{id}/start-checklist/manual/complete # Stop an experiment Source: https://docs.growthbook.io/api/experiments/operation/postExperimentStop openapi.yaml POST /v1/experiments/{id}/stop # Unarchive an experiment Source: https://docs.growthbook.io/api/experiments/operation/postExperimentUnarchive openapi.yaml POST /v1/experiments/{id}/unarchive # Upload a variation screenshot Source: https://docs.growthbook.io/api/experiments/operation/postVariationImageUpload openapi.yaml POST /v1/experiments/{id}/variation/{variationId}/screenshot/upload # Set an experiment's schedule and shipping automation Source: https://docs.growthbook.io/api/experiments/operation/putExperimentSchedule openapi.yaml PUT /v1/experiments/{id}/schedule Full-replace of the experiment's scheduled start/end and end-of-experiment shipping automation. The body is the complete desired state: any omitted field is cleared (omit `startAt` to remove a scheduled start; send an empty body to clear the whole schedule). Provide either `stopAt` or `stopAfter`, not both; a relative `stopAfter` resolves to a concrete stop when the experiment starts. Setting a scheduled end (`stopAt` or `stopAfter`) requires a `scheduledStopPlan` (any mode, including `notify`). The scheduled end must be in the future: a `stopAt` (or a `stopAfter` that resolves) in the past is rejected — including one whose end date has already passed and been acted on, so changing the plan after a soft end requires committing to a new end date. Auto-ship shipping requires the Decision Framework. # Update a single experiment Source: https://docs.growthbook.io/api/experiments/operation/updateExperiment openapi.yaml POST /v1/experiments/{id} Setting `status` from draft to running starts the experiment as `POST /experiments/{id}/start` does, judged on the experiment as this update leaves it, with `skipChecklist` in this body. Changing `archived` archives or unarchives it as `POST /experiments/{id}/archive` and `/unarchive` do. # Deletes a single fact metric Source: https://docs.growthbook.io/api/fact-metrics/operation/deleteFactMetric openapi.yaml DELETE /v1/fact-metrics/{id} # Get a single fact metric Source: https://docs.growthbook.io/api/fact-metrics/operation/getFactMetric openapi.yaml GET /v1/fact-metrics/{id} # Get all fact metrics Source: https://docs.growthbook.io/api/fact-metrics/operation/listFactMetrics openapi.yaml GET /v1/fact-metrics # Create a single fact metric Source: https://docs.growthbook.io/api/fact-metrics/operation/postFactMetric openapi.yaml POST /v1/fact-metrics # Create a fact metric analysis Source: https://docs.growthbook.io/api/fact-metrics/operation/postFactMetricAnalysis openapi.yaml POST /v1/fact-metrics/{id}/analysis # Update a single fact metric Source: https://docs.growthbook.io/api/fact-metrics/operation/updateFactMetric openapi.yaml POST /v1/fact-metrics/{id} # Deletes a single fact table Source: https://docs.growthbook.io/api/fact-tables/operation/deleteFactTable openapi.yaml DELETE /v1/fact-tables/{id} # Deletes a single fact table filter Source: https://docs.growthbook.io/api/fact-tables/operation/deleteFactTableFilter openapi.yaml DELETE /v1/fact-tables/{factTableId}/filters/{id} # Delete a virtual (computed) column from a fact table Source: https://docs.growthbook.io/api/fact-tables/operation/deleteFactTableVirtualColumn openapi.yaml DELETE /v1/fact-tables/{factTableId}/virtual-columns/{id} # Get the materialization status of a fact table's shared daily aggregated tables Source: https://docs.growthbook.io/api/fact-tables/operation/getAggregatedFactTables openapi.yaml GET /v1/fact-tables/{id}/aggregated-tables # Get a single aggregated table run Source: https://docs.growthbook.io/api/fact-tables/operation/getAggregatedTableRun openapi.yaml GET /v1/fact-tables/{id}/aggregated-tables/runs/{runId} # Get a single fact table Source: https://docs.growthbook.io/api/fact-tables/operation/getFactTable openapi.yaml GET /v1/fact-tables/{id} # Get a single fact filter Source: https://docs.growthbook.io/api/fact-tables/operation/getFactTableFilter openapi.yaml GET /v1/fact-tables/{factTableId}/filters/{id} # List aggregated table runs Source: https://docs.growthbook.io/api/fact-tables/operation/listAggregatedTableRuns openapi.yaml GET /v1/fact-tables/{id}/aggregated-tables/runs # Get all filters for a fact table Source: https://docs.growthbook.io/api/fact-tables/operation/listFactTableFilters openapi.yaml GET /v1/fact-tables/{factTableId}/filters # Get all fact tables Source: https://docs.growthbook.io/api/fact-tables/operation/listFactTables openapi.yaml GET /v1/fact-tables # Bulk import fact tables, filters, and metrics Source: https://docs.growthbook.io/api/fact-tables/operation/postBulkImportFacts openapi.yaml POST /v1/bulk-import/facts Creates or updates Fact Tables, Fact Table filters, and Fact Metrics. Resources upsert by `id`. Pass `dryRun: true` to validate with zero writes. Not transactional: a live mid-loop failure returns HTTP 400 (403 for a permission failure) with write counts and `errors`. # Create a single fact table Source: https://docs.growthbook.io/api/fact-tables/operation/postFactTable openapi.yaml POST /v1/fact-tables # Create a single fact table filter Source: https://docs.growthbook.io/api/fact-tables/operation/postFactTableFilter openapi.yaml POST /v1/fact-tables/{factTableId}/filters # Create a virtual (computed) column on a fact table Source: https://docs.growthbook.io/api/fact-tables/operation/postFactTableVirtualColumn openapi.yaml POST /v1/fact-tables/{factTableId}/virtual-columns # Force a refresh or full restate of a fact table's shared daily aggregated tables Source: https://docs.growthbook.io/api/fact-tables/operation/refreshAggregatedFactTable openapi.yaml POST /v1/fact-tables/{id}/aggregated-tables/refresh # Update a single fact table Source: https://docs.growthbook.io/api/fact-tables/operation/updateFactTable openapi.yaml POST /v1/fact-tables/{id} # Update a single fact table filter Source: https://docs.growthbook.io/api/fact-tables/operation/updateFactTableFilter openapi.yaml POST /v1/fact-tables/{factTableId}/filters/{id} # Update a virtual (computed) column on a fact table Source: https://docs.growthbook.io/api/fact-tables/operation/updateFactTableVirtualColumn openapi.yaml POST /v1/fact-tables/{factTableId}/virtual-columns/{id} # Delete an owned revision Comment entry Source: https://docs.growthbook.io/api/feature-revisions-v2/operation/deleteFeatureRevisionLogEntryV2 openapi.yaml DELETE /v2/features/{id}/revisions/{version}/log/{logId} Author of a `Comment` log entry can delete it. Verdict entries (Approved, Requested Changes, Review Requested) and other audit-trail events are immutable. To retract a verdict use `/undo-review`; to retract a review request use `/recall-review`. # Remove ramp schedule from a rule Source: https://docs.growthbook.io/api/feature-revisions-v2/operation/deleteFeatureRevisionRuleRampScheduleV2 openapi.yaml DELETE /v2/features/{id}/revisions/{version}/rules/{ruleId}/ramp-schedule Clears any pending ramp action for this rule. If a live ramp schedule exists, queues a detach that removes it on publish — the rule will show `pendingRamp: "detach"`. If only a pending create exists, it is removed and `pendingRamp` is cleared. # Delete a rule from a draft revision Source: https://docs.growthbook.io/api/feature-revisions-v2/operation/deleteFeatureRevisionRuleV2 openapi.yaml DELETE /v2/features/{id}/revisions/{version}/rules/{ruleId} Removes the rule from the revision. Any pending ramp actions for this rule are also cleared. # Diff a revision against another revision Source: https://docs.growthbook.io/api/feature-revisions-v2/operation/getFeatureRevisionDiffV2 openapi.yaml GET /v2/features/{id}/revisions/{version}/diff Returns a schema-keyed JSON diff between this revision and a baseline. The same shapes the in-app review surface produces under `Copy as → Minimal JSON` / `Full JSON`: `minimal` lists only what changed (with id-keyed arrays bucketed into added/removed/modified items and reorder detection), while `full` returns the complete before/after content of the revision. Lifecycle fields (version, status, comment, date, createdBy, publishedBy) are excluded from the diff body and echoed via `from` / `to` instead. Defaults to diffing against the revision's own `baseVersion`; pass `?base=live` to diff against the current live revision, or `?base=` for an arbitrary historical one. # Get the most recent active draft revision Source: https://docs.growthbook.io/api/feature-revisions-v2/operation/getFeatureRevisionLatestV2 openapi.yaml GET /v2/features/{id}/revisions/latest Returns the most recently updated active draft revision for the feature. Returns 404 if no matching draft exists. Filter by status, author, or use `mine=true` to scope to the calling user's own drafts. # List the activity log for a revision Source: https://docs.growthbook.io/api/feature-revisions-v2/operation/getFeatureRevisionLogV2 openapi.yaml GET /v2/features/{id}/revisions/{version}/log Returns every log entry for the revision — content edits (rules, default value, rebases), review lifecycle events (review requested, approved, changes requested, recalled, undone), comments, and other audit events — sorted oldest-first. # Get merge status for a draft revision Source: https://docs.growthbook.io/api/feature-revisions-v2/operation/getFeatureRevisionMergeStatusV2 openapi.yaml GET /v2/features/{id}/revisions/{version}/merge-status Runs the three-way merge between the draft and the current live version without applying it. Conflicts are granular: each conflicting field gets its own key, and rules conflict individually (`rules.`, plus `rules.order` for competing reorders). Pass the returned `liveVersion` as `expectedLiveVersion` when rebasing. Also reports `rebaseRequired` so callers can detect ahead of time whether the publish endpoint will block until the draft is rebased. # Get a single feature revision Source: https://docs.growthbook.io/api/feature-revisions-v2/operation/getFeatureRevisionV2 openapi.yaml GET /v2/features/{id}/revisions/{version} Returns the revision at the specified version for this feature. Revision `rules` is a flat array with per-rule environment scope. # List revisions for a feature Source: https://docs.growthbook.io/api/feature-revisions-v2/operation/getFeatureRevisionsV2 openapi.yaml GET /v2/features/{id}/revisions Returns a paginated list of revisions for this feature, sorted newest-first. Revision `rules` is a flat array with per-rule scope. # List revisions across all features Source: https://docs.growthbook.io/api/feature-revisions-v2/operation/listRevisionsV2 openapi.yaml GET /v2/feature-revisions Returns a paginated list of feature revisions across all features in the organization. Use the `featureId` query parameter to filter to a single feature. Revision `rules` is a flat array with per-rule scope. # Discard a draft revision Source: https://docs.growthbook.io/api/feature-revisions-v2/operation/postFeatureRevisionDiscardV2 openapi.yaml POST /v2/features/{id}/revisions/{version}/discard # Publish a draft revision Source: https://docs.growthbook.io/api/feature-revisions-v2/operation/postFeatureRevisionPublishV2 openapi.yaml POST /v2/features/{id}/revisions/{version}/publish Publishes the draft and makes its changes live. The caller needs Publish access for every affected environment. When approval is required, the draft must be approved unless the caller has Bypass draft approvals access. If the organization requires rebasing, an out-of-date draft must be rebased first; an authorized caller can instead send `ignoreWarnings: true` to force-publish it. Any pending ramp actions in `pendingRamp` are applied as part of the same operation. A 422 response lists every blocking gate and the available resolution. # Preview a rebase without applying it Source: https://docs.growthbook.io/api/feature-revisions-v2/operation/postFeatureRevisionRebasePreviewV2 openapi.yaml POST /v2/features/{id}/revisions/{version}/rebase/preview Dry-run of the rebase: runs the same three-way merge with the supplied `conflictResolutions` and returns every conflict (resolved and unresolved) plus the merged result once all are resolved — without modifying the draft. Use it to iterate on resolutions before committing them via the rebase endpoint. # Rebase a draft revision onto the current live version Source: https://docs.growthbook.io/api/feature-revisions-v2/operation/postFeatureRevisionRebaseV2 openapi.yaml POST /v2/features/{id}/revisions/{version}/rebase Updates the draft's base revision to match the currently-live revision, applying the draft's changes on top. Supply `conflictResolutions` to resolve conflicting items individually — including per-rule (`rules.`) and rule-order (`rules.order`) conflicts. Supply `expectedLiveVersion` and/or `expectedDraftDateUpdated` (both returned by merge-status and rebase preview) to fail fast with `409` if either side changes between conflict review and submission. Unresolved conflicts also respond with `409`. # Recall a review request (revert to draft) Source: https://docs.growthbook.io/api/feature-revisions-v2/operation/postFeatureRevisionRecallReviewV2 openapi.yaml POST /v2/features/{id}/revisions/{version}/recall-review Retracts the review request, returning the revision from `pending-review`, `changes-requested`, or `approved` back to `draft`. Allowed for any user with draft-management permission on the feature (the same permission required to request review), not only the original requester. Existing review log entries are preserved as audit history but any in-flight reviewer verdicts (Approved / Requested Changes) submitted during this review cycle no longer count — submitting a fresh `request-review` starts a new cycle. # Reopen a discarded revision as a draft Source: https://docs.growthbook.io/api/feature-revisions-v2/operation/postFeatureRevisionReopenV2 openapi.yaml POST /v2/features/{id}/revisions/{version}/reopen Returns a `discarded` revision to `draft` status so it can be edited, reviewed, and published. Prior review state is not restored — the draft must go back through review if approvals are required. # Request review for a draft revision Source: https://docs.growthbook.io/api/feature-revisions-v2/operation/postFeatureRevisionRequestReviewV2 openapi.yaml POST /v2/features/{id}/revisions/{version}/request-review Moves the draft into the `pending-review` state and notifies reviewers. Set `autoPublishOnApproval` to `true` to publish the revision automatically the moment it is approved (GitHub auto-merge model). This requires the org to have auto-publish-on-approval enabled for the feature and the caller to have publish permission; the auto-publish then executes with the caller's authority. Set `scheduledPublishAt` to a future ISO date-time to defer the auto-publish until that date (it still also requires approval when review is required). Use `scheduledPublishLockEdits` to freeze edits to this draft while the schedule is pending, and `scheduledPublishLockOthers` to block publishing other drafts of this feature in the meantime. Neither can be set on a draft whose rules would take a draft experiment live, since an automatic publish doesn't start the experiment. Schedule the experiment's start instead; starting it publishes the draft. # Revert the feature to a prior revision Source: https://docs.growthbook.io/api/feature-revisions-v2/operation/postFeatureRevisionRevertV2 openapi.yaml POST /v2/features/{id}/revisions/{version}/revert # Add a rule to a draft revision Source: https://docs.growthbook.io/api/feature-revisions-v2/operation/postFeatureRevisionRuleAddV2 openapi.yaml POST /v2/features/{id}/revisions/{version}/rules Appends a new rule to the revision's rule list. Supply `allEnvironments: true` on the rule to target all environments, or `environments: [...]` to scope to specific ones. **Scheduling:** For `force` and `rollout` rules, attach a schedule via `rampSchedule` (multi-step ramp) or `schedule` (simple start/end window) — these create standalone ramp actions and set `pendingRamp: "create"` on the rule. For `experiment-ref` and `safe-rollout` rules, only `schedule` is supported and is stored as legacy schedule fields on the rule itself (`rampSchedule` is not available for these rule types). # Reorder rules in the revision Source: https://docs.growthbook.io/api/feature-revisions-v2/operation/postFeatureRevisionRulesReorderV2 openapi.yaml POST /v2/features/{id}/revisions/{version}/rules/reorder Replaces the flat global rule order. `ruleIds` must contain **exactly** the set of all existing rule IDs in the revision — no additions, omissions, or duplicates. # Schedule (or cancel) a deferred publish for a draft revision Source: https://docs.growthbook.io/api/feature-revisions-v2/operation/postFeatureRevisionSchedulePublishV2 openapi.yaml POST /v2/features/{id}/revisions/{version}/schedule-publish Schedules the draft to publish on or after `scheduledPublishAt`. When approval is required, publishing waits until the draft is also approved. Send `scheduledPublishAt: null` to cancel the schedule. Set `lockEdits` to prevent content changes while the schedule is pending; rebasing remains allowed. Set `lockOthers` to prevent other drafts of this Feature Flag from being published until this schedule runs or is canceled. The caller needs Publish access, and that access is checked again when the schedule runs. A caller with Bypass draft approvals access can schedule an unapproved draft by sending `bypassApproval: true`. That schedule must be canceled and recreated before it can be changed, and a later edit that would reset an approval also drops the bypass. A draft whose rules would take a draft experiment live can't be scheduled, since a scheduled publish doesn't start the experiment. Schedule the experiment's start instead; starting it publishes the draft. # Submit a review on a draft revision Source: https://docs.growthbook.io/api/feature-revisions-v2/operation/postFeatureRevisionSubmitReviewV2 openapi.yaml POST /v2/features/{id}/revisions/{version}/submit-review Submits an `approve`, `request-changes`, or `comment` review on the draft. The verdict belongs to whoever the request acts as; see [Naming who asked](/api/introduction#naming-who-asked). No one can rule on a draft they created. Contributors cannot approve their own drafts when `blockSelfApproval` is enabled. When `action` is `approve` and the revision has `autoPublishOnApproval` enabled, the revision is automatically published after approval. The response includes `autoPublished: true` when this happens. Pass `skipAutoPublish: true` to approve without triggering auto-publish. # Toggle an environment on/off in a draft revision Source: https://docs.growthbook.io/api/feature-revisions-v2/operation/postFeatureRevisionToggleV2 openapi.yaml POST /v2/features/{id}/revisions/{version}/toggle # Undo a reviewer's own review verdict Source: https://docs.growthbook.io/api/feature-revisions-v2/operation/postFeatureRevisionUndoReviewV2 openapi.yaml POST /v2/features/{id}/revisions/{version}/undo-review Reviewer retracts their own verdict. The verdict belongs to whoever the request acts as; see [Naming who asked](/api/introduction#naming-who-asked). No one can rule on a draft they created. The revision status rewinds to the state implied by the remaining active verdicts from other reviewers: any outstanding `Requested Changes` → `changes-requested`, else any outstanding `Approved` → `approved`, else `pending-review`. Existing review comments are preserved. If the retraction resolves the revision to `approved` and auto-publish-on-approval is armed, the revision is published. # Create a draft revision Source: https://docs.growthbook.io/api/feature-revisions-v2/operation/postFeatureRevisionV2 openapi.yaml POST /v2/features/{id}/revisions Creates a new draft revision branched from the current live revision. # Set archived state in a draft revision Source: https://docs.growthbook.io/api/feature-revisions-v2/operation/putFeatureRevisionArchiveV2 openapi.yaml PUT /v2/features/{id}/revisions/{version}/archive # Set the default value in a draft revision Source: https://docs.growthbook.io/api/feature-revisions-v2/operation/putFeatureRevisionDefaultValueV2 openapi.yaml PUT /v2/features/{id}/revisions/{version}/default-value # Set holdout in a draft revision Source: https://docs.growthbook.io/api/feature-revisions-v2/operation/putFeatureRevisionHoldoutV2 openapi.yaml PUT /v2/features/{id}/revisions/{version}/holdout # Edit the comment text of an owned log entry Source: https://docs.growthbook.io/api/feature-revisions-v2/operation/putFeatureRevisionLogCommentV2 openapi.yaml PUT /v2/features/{id}/revisions/{version}/log/{logId} Author of a `Comment`, `Approved`, or `Requested Changes` log entry can rewrite its comment text. The entry's action and other audit-trail metadata remain immutable; this only mutates `value.comment`. Other audit events (e.g. `Review Requested`, system events) are not editable. # Update revision metadata Source: https://docs.growthbook.io/api/feature-revisions-v2/operation/putFeatureRevisionMetadataV2 openapi.yaml PUT /v2/features/{id}/revisions/{version}/metadata # Set feature-level prerequisites in a draft revision Source: https://docs.growthbook.io/api/feature-revisions-v2/operation/putFeatureRevisionPrerequisitesV2 openapi.yaml PUT /v2/features/{id}/revisions/{version}/prerequisites Sets the feature-level prerequisites for this revision. Each prerequisite must be a boolean feature flag; the gate is always 'prerequisite flag is on'. The condition is applied automatically — only the flag ID is required. # Set ramp schedule for a rule Source: https://docs.growthbook.io/api/feature-revisions-v2/operation/putFeatureRevisionRuleRampScheduleV2 openapi.yaml PUT /v2/features/{id}/revisions/{version}/rules/{ruleId}/ramp-schedule Queues a revision-controlled ramp action for this rule. If the rule already has a live ramp schedule, this stores an `update` action applied on publish; otherwise it stores a `create` action. No live schedule config changes are applied immediately by this endpoint. You can build the ramp from a template (`templateId`) and set the rollback anchor (`startState`) in the same request — e.g. pull in a template and pass `startState: { "coverage": 0 }` so a rollback returns the rule to 0%. # Update a rule in a draft revision Source: https://docs.growthbook.io/api/feature-revisions-v2/operation/putFeatureRevisionRuleV2 openapi.yaml PUT /v2/features/{id}/revisions/{version}/rules/{ruleId} Patches fields on an existing rule (identified by `ruleId`). The rule `type` cannot be changed. Scope can be updated via `allEnvironments` / `environments` patch fields. **Scheduling:** For `force` and `rollout` rules, update the schedule via `rampSchedule` (multi-step ramp) or `schedule` (simple start/end window) — these manage standalone ramp actions and set `pendingRamp: "create"` on the rule. For `experiment-ref` and `safe-rollout` rules, only `schedule` is supported and updates legacy schedule fields on the rule itself (`rampSchedule` is not available for these rule types). # Delete a rule from a draft revision Source: https://docs.growthbook.io/api/feature-revisions/operation/deleteFeatureRevisionRule openapi.yaml DELETE /v1/features/{id}/revisions/{version}/rules/{ruleId} **Deprecated.** Use [DELETE /v2/features/:id/revisions/:version/rules/:ruleId](#operation/deleteFeatureRevisionRuleV2) instead, which removes the rule from the flat array without an `environment` parameter. Removes the rule from the specified environment. Any pending ramp actions on the draft for this rule are also cleared. # Remove ramp schedule from a rule Source: https://docs.growthbook.io/api/feature-revisions/operation/deleteFeatureRevisionRuleRampSchedule openapi.yaml DELETE /v1/features/{id}/revisions/{version}/rules/{ruleId}/ramp-schedule **Deprecated.** Use [DELETE /v2/features/:id/revisions/:version/rules/:ruleId/ramp-schedule](#operation/deleteFeatureRevisionRuleRampScheduleV2) instead. Removes a pending ramp schedule attached by the draft. If the rule currently has a live ramp schedule, a detach action is queued and applied at publish time. # Get a single feature revision Source: https://docs.growthbook.io/api/feature-revisions/operation/getFeatureRevision openapi.yaml GET /v1/features/{id}/revisions/{version} **Deprecated.** Use [GET /v2/features/:id/revisions/:version](#operation/getFeatureRevisionV2) instead. Returns the revision at the specified version for this feature. Use `GET /features/{id}/revisions/latest` for the most recent active draft. # Get the most recent active draft revision Source: https://docs.growthbook.io/api/feature-revisions/operation/getFeatureRevisionLatest openapi.yaml GET /v1/features/{id}/revisions/latest **Deprecated.** Use [GET /v2/features/:id/revisions/latest](#operation/getFeatureRevisionLatestV2) instead. Returns the most recently updated draft revision for the feature. Returns 404 if there is no active draft. Pass `mine=true` to return the most recent draft authored by or contributed to by the calling user. Needs a personal access token, or an organization API key that assumes a named member's role. # Get merge status for a draft revision Source: https://docs.growthbook.io/api/feature-revisions/operation/getFeatureRevisionMergeStatus openapi.yaml GET /v1/features/{id}/revisions/{version}/merge-status **Deprecated.** Use [GET /v2/features/:id/revisions/:version/merge-status](#operation/getFeatureRevisionMergeStatusV2) instead. Runs a dry-run merge of the draft against the current live revision and returns any conflicts. Use this before publishing to preview changes and detect conflicting edits. # List revisions for a feature Source: https://docs.growthbook.io/api/feature-revisions/operation/getFeatureRevisions openapi.yaml GET /v1/features/{id}/revisions **Deprecated.** Use [GET /v2/features/:id/revisions](#operation/getFeatureRevisionsV2) instead. Returns a paginated list of revisions for this feature, sorted newest-first. Optionally filtered by status and/or author. # List feature revisions Source: https://docs.growthbook.io/api/feature-revisions/operation/listRevisions openapi.yaml GET /v1/revisions **Deprecated.** Use [GET /v2/feature-revisions](#operation/listRevisionsV2) instead. Returns a paginated list of feature revisions across all features in the organization. Optionally filtered by feature, status, author, and/or the calling user's involvement. Results are sorted newest-first. # Create a draft revision Source: https://docs.growthbook.io/api/feature-revisions/operation/postFeatureRevision openapi.yaml POST /v1/features/{id}/revisions **Deprecated.** Use [POST /v2/features/:id/revisions](#operation/postFeatureRevisionV2) instead. Creates a new draft revision branched from the current live revision. A feature can have multiple concurrent drafts; use this to start an isolated line of edits. # Discard a draft revision Source: https://docs.growthbook.io/api/feature-revisions/operation/postFeatureRevisionDiscard openapi.yaml POST /v1/features/{id}/revisions/{version}/discard **Deprecated.** Use [POST /v2/features/:id/revisions/:version/discard](#operation/postFeatureRevisionDiscardV2) instead. Permanently discards a draft revision. Only drafts (never published revisions) can be discarded. Any pending ramp actions staged on the draft are dropped. # Publish a draft revision Source: https://docs.growthbook.io/api/feature-revisions/operation/postFeatureRevisionPublish openapi.yaml POST /v1/features/{id}/revisions/{version}/publish **Deprecated.** Use [POST /v2/features/:id/revisions/:version/publish](#operation/postFeatureRevisionPublishV2) instead. Publishes the draft and makes its changes live. The caller needs Publish access for every affected environment. When approval is required, the draft must be approved unless the caller has Bypass draft approvals access. # Rebase a draft revision onto the current live version Source: https://docs.growthbook.io/api/feature-revisions/operation/postFeatureRevisionRebase openapi.yaml POST /v1/features/{id}/revisions/{version}/rebase **Deprecated.** Use [POST /v2/features/:id/revisions/:version/rebase](#operation/postFeatureRevisionRebaseV2) instead. Updates the draft's base revision to match the currently-live revision, applying the draft's changes on top. Supply `conflictResolutions` to resolve any conflicting fields. **Conflict key format changed for v1 clients.** The per-rule `envName.ruleId` keys used by older clients are no longer recognized. Valid keys: `defaultValue`, `prerequisites`, `archived`, `holdout`, `environmentsEnabled.`, `metadata.`, `rules.`, `rules.order`, and the blanket `rules` (applies one strategy to all rule-level conflicts). Unrecognized keys are ignored; unresolved conflicts respond with `409`. # Request review for a draft revision Source: https://docs.growthbook.io/api/feature-revisions/operation/postFeatureRevisionRequestReview openapi.yaml POST /v1/features/{id}/revisions/{version}/request-review **Deprecated.** Use [POST /v2/features/:id/revisions/:version/request-review](#operation/postFeatureRevisionRequestReviewV2) instead. Moves the draft into the `pending-review` state and notifies reviewers. # Revert the feature to a prior revision Source: https://docs.growthbook.io/api/feature-revisions/operation/postFeatureRevisionRevert openapi.yaml POST /v1/features/{id}/revisions/{version}/revert **Deprecated.** Use [POST /v2/features/:id/revisions/:version/revert](#operation/postFeatureRevisionRevertV2) instead. Creates a new draft (or immediately publishes) whose content matches the specified historical revision. # Add a rule to a draft revision Source: https://docs.growthbook.io/api/feature-revisions/operation/postFeatureRevisionRuleAdd openapi.yaml POST /v1/features/{id}/revisions/{version}/rules **Deprecated.** Use [POST /v2/features/:id/revisions/:version/rules](#operation/postFeatureRevisionRuleAddV2) instead, which accepts rules with unified `allEnvironments`/`environments` scope fields instead of a per-environment `environment` parameter. Appends a new rule to the end of the rule list for the given environment. A `rule.type` of `force`, `rollout`, `experiment-ref`, or `safe-rollout` determines the accepted shape. Use `rampSchedule` for ramp configuration or `schedule` for a simple start/end window; if both are provided, `rampSchedule` wins. # Reorder rules in an environment Source: https://docs.growthbook.io/api/feature-revisions/operation/postFeatureRevisionRulesReorder openapi.yaml POST /v1/features/{id}/revisions/{version}/rules/reorder **Deprecated.** Use [POST /v2/features/:id/revisions/:version/rules/reorder](#operation/postFeatureRevisionRulesReorderV2) instead, which reorders the global flat rule array without an `environment` parameter. Replaces the rule order for the environment. `ruleIds` must contain **exactly** the set of existing rule IDs in that environment — no additions, omissions, or duplicates. # Submit a review on a draft revision Source: https://docs.growthbook.io/api/feature-revisions/operation/postFeatureRevisionSubmitReview openapi.yaml POST /v1/features/{id}/revisions/{version}/submit-review **Deprecated.** Use [POST /v2/features/:id/revisions/:version/submit-review](#operation/postFeatureRevisionSubmitReviewV2) instead. Submits an `approve`, `request-changes`, or `comment` review on the draft. The verdict belongs to whoever the request acts as; see [Naming who asked](/api/introduction#naming-who-asked). No one can rule on a draft they created. Contributors cannot approve their own drafts, but may submit comments or request changes. When `action` is `approve` and the revision has `autoPublishOnApproval` enabled, the revision is automatically published after approval. Pass `skipAutoPublish: true` to approve without triggering auto-publish. # Toggle an environment on/off in a draft revision Source: https://docs.growthbook.io/api/feature-revisions/operation/postFeatureRevisionToggle openapi.yaml POST /v1/features/{id}/revisions/{version}/toggle **Deprecated.** Use [POST /v2/features/:id/revisions/:version/toggle](#operation/postFeatureRevisionToggleV2) instead. Sets whether the feature is enabled in the given environment as part of the draft. Takes effect on publish. # Set archived state in a draft revision Source: https://docs.growthbook.io/api/feature-revisions/operation/putFeatureRevisionArchive openapi.yaml PUT /v1/features/{id}/revisions/{version}/archive **Deprecated.** Use [PUT /v2/features/:id/revisions/:version/archive](#operation/putFeatureRevisionArchiveV2) instead. Sets whether the feature is archived. Archived features are excluded from SDK payloads on publish. # Set the default value in a draft revision Source: https://docs.growthbook.io/api/feature-revisions/operation/putFeatureRevisionDefaultValue openapi.yaml PUT /v1/features/{id}/revisions/{version}/default-value **Deprecated.** Use [PUT /v2/features/:id/revisions/:version/default-value](#operation/putFeatureRevisionDefaultValueV2) instead. Replaces the feature's default value for this revision. The value must be a string representation matching the feature's value type (e.g. `"true"` for booleans, `42` for numbers, a JSON string for JSON features). # Set holdout in a draft revision Source: https://docs.growthbook.io/api/feature-revisions/operation/putFeatureRevisionHoldout openapi.yaml PUT /v1/features/{id}/revisions/{version}/holdout **Deprecated.** Use [PUT /v2/features/:id/revisions/:version/holdout](#operation/putFeatureRevisionHoldoutV2) instead. Sets (or clears, via `holdout: null`) the holdout experiment bound to the feature. Holdout linkage side-effects (updating the holdout's linked feature list) are applied on publish. # Update revision metadata (comment, title, feature metadata) Source: https://docs.growthbook.io/api/feature-revisions/operation/putFeatureRevisionMetadata openapi.yaml PUT /v1/features/{id}/revisions/{version}/metadata **Deprecated.** Use [PUT /v2/features/:id/revisions/:version/metadata](#operation/putFeatureRevisionMetadataV2) instead. Updates draft-level metadata (`comment`, `title`) and/or feature-level metadata (owner, project, tags, customFields, jsonSchema, etc.). Merge semantics: omitted fields are left unchanged; any provided field replaces the current value (pass an empty string/array/object to clear). Feature metadata changes are staged on the revision and applied to the feature on publish. Changing `project` requires publish permission on both the old and new project. # Set feature-level prerequisites in a draft revision Source: https://docs.growthbook.io/api/feature-revisions/operation/putFeatureRevisionPrerequisites openapi.yaml PUT /v1/features/{id}/revisions/{version}/prerequisites **Deprecated.** Use [PUT /v2/features/:id/revisions/:version/prerequisites](#operation/putFeatureRevisionPrerequisitesV2) instead. Replaces the feature's prerequisite list for this revision. Each prerequisite condition is evaluated against `{ value: }` at SDK eval time — use `value` as the condition key. # Update a rule in a draft revision Source: https://docs.growthbook.io/api/feature-revisions/operation/putFeatureRevisionRule openapi.yaml PUT /v1/features/{id}/revisions/{version}/rules/{ruleId} **Deprecated.** Use [PUT /v2/features/:id/revisions/:version/rules/:ruleId](#operation/putFeatureRevisionRuleV2) instead, which locates rules by `ruleId` in the flat array without an `environment` parameter. Patches fields on an existing rule. The rule `type` cannot be changed — to convert types, delete and re-add. Fields that don't apply to the current rule type are rejected. # Set ramp schedule for a rule Source: https://docs.growthbook.io/api/feature-revisions/operation/putFeatureRevisionRuleRampSchedule openapi.yaml PUT /v1/features/{id}/revisions/{version}/rules/{ruleId}/ramp-schedule **Deprecated.** Use [PUT /v2/features/:id/revisions/:version/rules/:ruleId/ramp-schedule](#operation/putFeatureRevisionRuleRampScheduleV2) instead. Queues a revision-controlled ramp action for this rule. If the rule already has a live ramp schedule, this stores an `update` action applied on publish; otherwise it stores a `create` action. No live schedule config changes are applied immediately by this endpoint. # Deletes a single feature Source: https://docs.growthbook.io/api/features-v2/operation/deleteFeatureV2 openapi.yaml DELETE /v2/features/{id} Permanently deletes a Feature Flag and all of its revisions. The caller needs Archive & delete access. Deleting a live Feature Flag also requires Publish access for every environment where it is enabled and the organization setting "REST API always bypasses approval requirements". Otherwise, archive the Feature Flag before deleting it. # Get list of feature keys Source: https://docs.growthbook.io/api/features-v2/operation/getFeatureKeysV2 openapi.yaml GET /v2/feature-keys # Get stale status for one or more features Source: https://docs.growthbook.io/api/features-v2/operation/getFeatureStaleV2 openapi.yaml GET /v2/stale-features # Get a single feature Source: https://docs.growthbook.io/api/features-v2/operation/getFeatureV2 openapi.yaml GET /v2/features/{id} # Get all features Source: https://docs.growthbook.io/api/features-v2/operation/listFeaturesV2 openapi.yaml GET /v2/features Returns features with pagination. Rules are returned as a unified top-level array with per-rule environment scope. # Create a single feature Source: https://docs.growthbook.io/api/features-v2/operation/postFeatureV2 openapi.yaml POST /v2/features Creates a new Feature Flag. The caller needs Create access in its Project, plus Publish access for any environment the Feature Flag starts enabled in — one that starts disabled everywhere needs Create alone. Rules are supplied as a top-level `rules` array; each rule includes `allEnvironments` / `environments` scope fields. ### Naming for human review The `id` is the Feature Flag's permanent name in the UI and code. Keep it short (`checkout-express-pay`) and put context in `description`. To create it disabled, list every environment in `environments` with `enabled: false`. ### Config-backed features (Config mode) A JSON feature can be backed by a shared **config** — the config supplies the base JSON value and schema, and the feature's *rule* values become override *patches* merged on top (nested objects deep-merge; arrays and scalars replace). The default value is exactly a config with no overrides (see below). Config backing is set exclusively through dedicated fields — never a raw `$extends: ["@config:…"]` inside a value string (that is rejected). `@const:` references inside values still work. - **Top-level (`baseConfig`):** set `valueType: "json"` and `baseConfig: ""` to put the Feature Flag in Config mode. The config must be live. This is the family root and the base the default value patches. - **Default value:** unlike rules, the default is exactly a config with no overrides of its own — send `defaultValue: "{}"` to use `baseConfig`. To resolve the default to a *descendant* of `baseConfig` instead, set `defaultValueConfig` to that descendant's key (it must be within `baseConfig`'s family); omit/null to use `baseConfig` directly. - **Rules & experiment variations:** each carries its own `config` field naming the family config that value patches (omit/null to patch the base). `value` is the override patch. Example: ```json { "id": "checkout-config", "valueType": "json", "baseConfig": "purchase-flow", "defaultValue": "{}", "rules": [ { "type": "force", "config": "purchase-flow-vip", "value": "{\"maxItems\": 20}", "allEnvironments": true } ] } ``` # Revert a feature to a specific revision Source: https://docs.growthbook.io/api/features-v2/operation/revertFeatureV2 openapi.yaml POST /v2/features/{id}/revert Restores a previously published revision and immediately publishes the result as a new revision. The caller needs Revert access for every affected environment. When approval is required, the request is allowed only if the caller holds the `FlagsBypassApprovals` policy, or the organization enables either "REST API always bypasses approval requirements" or "Allow reverts without approval". If the restored values no longer match the Feature Flag's current value type or JSON schema, or restoring an archived state would archive a flag that live flags or experiments still depend on, the API returns 422 with `warnings`. Send `"ignoreWarnings": true` to acknowledge those warnings and continue. # Toggle a feature in one or more environments Source: https://docs.growthbook.io/api/features-v2/operation/toggleFeatureV2 openapi.yaml POST /v2/features/{id}/toggle Enables or disables a Feature Flag in one or more environments and immediately publishes the change. The caller needs Publish access for every environment in the request. When approval is required, use a draft revision instead, unless the caller can bypass draft approvals. # Partially update a feature Source: https://docs.growthbook.io/api/features-v2/operation/updateFeatureV2 openapi.yaml POST /v2/features/{id} Updates the Feature Flag and immediately publishes a new revision. The caller needs Edit access in the Feature Flag's Project and Publish access for every affected environment. When approval is required, use the revision endpoints instead, unless the caller can bypass draft approvals. Other top-level fields are patch-merged: omit a field to leave it unchanged. The `rules` field, when supplied, replaces the entire `rules` array in one operation. To preserve existing rules, fetch the Feature Flag, update the returned `rules` array, and send the complete array back. Safe-rollout rules round-trip through `safeRolloutId`; use `POST /v2/features/:id/revisions/:version/rules` to create new ones. # Deletes a single feature Source: https://docs.growthbook.io/api/features/operation/deleteFeature openapi.yaml DELETE /v1/features/{id} **Deprecated.** Use [DELETE /v2/features/:id](#operation/deleteFeatureV2) instead. Permanently deletes a Feature Flag and all of its revisions. The caller needs Archive & delete access. Deleting a live Feature Flag also requires Publish access for every environment where it is enabled and the organization setting "REST API always bypasses approval requirements". Otherwise, archive the Feature Flag before deleting it. # Get a single feature Source: https://docs.growthbook.io/api/features/operation/getFeature openapi.yaml GET /v1/features/{id} **Deprecated.** Use [GET /v2/features/:id](#operation/getFeatureV2) instead. # Get list of feature keys Source: https://docs.growthbook.io/api/features/operation/getFeatureKeys openapi.yaml GET /v1/feature-keys **Deprecated.** Use [GET /v2/feature-keys](#operation/getFeatureKeysV2) instead. # Get stale status for one or more features Source: https://docs.growthbook.io/api/features/operation/getFeatureStale openapi.yaml GET /v1/stale-features **Deprecated.** Use [GET /v2/stale-features](#operation/getFeatureStaleV2) instead. # Get all features Source: https://docs.growthbook.io/api/features/operation/listFeatures openapi.yaml GET /v1/features **Deprecated.** Use [GET /v2/features](#operation/listFeaturesV2) instead. Returns features with pagination. The skipPagination query parameter is honored only when API_ALLOW_SKIP_PAGINATION is set (self-hosted deployments). # Create a single feature Source: https://docs.growthbook.io/api/features/operation/postFeature openapi.yaml POST /v1/features **Deprecated.** Use [POST /v2/features](#operation/postFeatureV2) instead. # Revert a feature to a specific revision Source: https://docs.growthbook.io/api/features/operation/revertFeature openapi.yaml POST /v1/features/{id}/revert **Deprecated.** Use [POST /v2/features/:id/revert](#operation/revertFeatureV2) instead. Restores a previously published revision and immediately publishes the result as a new revision. The caller needs Revert access for every affected environment. When approval is required, the request is allowed only if the caller holds the `FlagsBypassApprovals` policy, or the organization enables either "REST API always bypasses approval requirements" or "Allow reverts without approval". If the restored values no longer match the Feature Flag's current value type or JSON schema, or restoring an archived state would archive a flag that live flags or experiments still depend on, the API returns 422 with `warnings`. Send `"ignoreWarnings": true` to acknowledge those warnings and continue. # Toggle a feature in one or more environments Source: https://docs.growthbook.io/api/features/operation/toggleFeature openapi.yaml POST /v1/features/{id}/toggle **Deprecated.** Use [POST /v2/features/:id/toggle](#operation/toggleFeatureV2) instead. Enables or disables a Feature Flag in one or more environments and immediately publishes the change. The caller needs Publish access for every environment in the request. When approval is required, use a draft revision instead, unless the caller can bypass draft approvals. # Partially update a feature Source: https://docs.growthbook.io/api/features/operation/updateFeature openapi.yaml POST /v1/features/{id} **Deprecated.** Use [POST /v2/features/:id](#operation/updateFeatureV2) instead. Updates the Feature Flag and immediately publishes a new revision. The caller needs Edit access in the Feature Flag's Project and Publish access for every affected environment. When approval is required, use the revision endpoints instead, unless the caller can bypass draft approvals. # Introduction Source: https://docs.growthbook.io/api/introduction GrowthBook offers a full REST API for interacting with the application. GrowthBook offers a full REST API for interacting with the application. Request data can use either JSON or Form data encoding (with proper `Content-Type` headers). All response bodies are JSON-encoded. The API base URL for GrowthBook Cloud is `https://api.growthbook.io/api`. For self-hosted deployments, it is the same as your API\_HOST environment variable (defaults to `http://localhost:3100/api`). The rest of these docs will assume you are using GrowthBook Cloud. ## Versioning Endpoints are versioned by path prefix: * `/v1/...` — stable, widely-supported endpoints * `/v2/...` — updated endpoints with improved shapes (e.g. unified per-rule environment scope for feature flags) New integrations should prefer v2 where available. Feature-flag and metric v1 routes are deprecated: they are tagged **LEGACY** in the sidebar and collapsed by default. Use the matching v2 group when both exist. ## Authentication We support both the HTTP Basic and Bearer authentication schemes for convenience. You first need to generate a new API Key in GrowthBook. Different keys have different permissions: * **Personal Access Tokens**: These are sensitive and provide the same level of access as the user has to an organization, or less if the token's permissions are limited. These can be created by going to `Personal Access Tokens` under your user menu. Members with the API Keys **Manage members' tokens** permission can disable a member's token under `Settings -> Manage PATs`, and admins can require new tokens to expire within a maximum lifetime. * **Secret Keys**: These are sensitive and provide the level of access of the role they are given, which can be any built-in or custom role, with optional per-project role overrides and environment limits. Managing Secret Keys on behalf of an organization takes the [API Keys permissions](/account/user-permissions). These can be created by going to `Settings -> API Keys`, where Admins can also require new Secret Keys to expire within a maximum lifetime. If using HTTP Basic auth, pass the Secret Key as the username and leave the password blank (when using curl, add `:` at the end of the secret to indicate an empty password) ```bash theme={null} curl https://api.growthbook.io/api/v1/features \ -u secret_abc123DEF456: ``` If using Bearer auth, pass the Secret Key as the token: ```bash theme={null} curl https://api.growthbook.io/api/v1/features \ -H "Authorization: Bearer secret_abc123DEF456" ``` If a key has an expiration date, every response includes it in the `X-GrowthBook-Key-Expires-At` header (ISO 8601), so scripts can warn before the key stops working. Requests made with an expired key fail with a `401` that says where to create a replacement. See [Expiration](/integrations/ai-agents/api-keys#expiration) for choosing a key's expiration, the warnings before it lapses, and the organization policies. ## Naming who asked Requests made with a Secret Key may add an `X-GrowthBook-Requested-By` header naming the organization member who asked for the change, as a user id or an email address. History, revision logs and webhooks show that member next to the key. Each key acts as **System** (the default) or **User**, set under `Settings -> API Keys`: * **System**: the header is optional. The key acts as itself, with its own permissions, and the named member is only recorded in history. * **User**: a request without the header is rejected with a 400. The request acts as the named member, with only what both the key and the member can do, so the header can narrow a key but never widen it. Drafts, owners, `mine` filters and reviews are the member's. The member's rights over their own drafts, comments and verdicts apply only where the key's own role allows the same action. A System key, or any key that names no one, acts as itself: it can draft and review with its own permissions, and its verdicts are judged on its role. A key belongs to no team, so its approval never satisfies a rule that requires approval from a team. No one can rule on a draft they created, and a key acting as itself can't rule on a draft it created or edited. An approval a User key gives counts only where both the key and the member could approve, and stops counting if the key is disabled, expires or is deleted. Because the header isn't verified, whoever holds the key can act as any member. A value that does not match a member of the organization is rejected with a 400. A create request that omits `owner` defaults it to the member a User key names. Scheduled and auto-publishes run later with the permissions of the request that armed them, and don't run if that member has left the organization, the key has expired, or the key no longer acts as User. Personal Access Tokens and OAuth tokens already act as their user and reject the header. ```bash theme={null} curl https://api.growthbook.io/api/v1/features \ -H "Authorization: Bearer secret_abc123DEF456" \ -H "X-GrowthBook-Requested-By: alice@example.com" ``` ## Naming and human review People review what the API creates in the GrowthBook UI. * Keep keys and names short (2-5 words). Follow your organization's Feature Flag key format (`featureRegexValidator` and `featureKeyExample` in `GET /v1/settings`), or use kebab-case if it has none. Feature Flag keys can't be changed after creation, and an experiment's tracking key shouldn't change after launch. * Put context (why, plan, tickets, links) in `description`, not in names or keys. * Group with `tags`, not name prefixes. Projects (`project` or `projects`) control access and where a resource can be used, so set them deliberately instead of using them as labels. * To create a Feature Flag disabled with `POST /v2/features`, list every environment in `environments` with `enabled: false` (on `POST /v1/features`, each entry also needs `rules: []`). An environment you leave out uses its default state, which may be enabled. * Leave publishing and production changes to a person unless asked. * If a request is blocked (an approval requirement, a warning, a Custom Hook, schema validation or a permission error), stop and ask a person. Don't retry with an override field, a different key or another workaround unless a person asked you to. If a write answers `202` with a `confirmation`, give a person its `url` and wait (see [Waiting for confirmation](#waiting-for-confirmation)). ## Waiting for confirmation A key can require a person to confirm some writes before they run (see [API Keys for Agents](/integrations/ai-agents/api-keys#require-confirmation)). A held write doesn't run. It answers `202 Accepted` with the confirmation, a `Location` header to poll, and a `Retry-After` header with the seconds to wait between polls: ```json theme={null} { "confirmation": { "id": "cnf_abc123", "status": "pending", "actions": [ { "action": "feature.publish", "environments": ["production"] } ], "summary": "Publish revision 3 of checkout-redesign", "url": "https://app.growthbook.io/confirmations/cnf_abc123", "expiresAt": "2026-10-15T18:00:00.000Z", "response": null, "note": null }, "message": "A person must confirm this in GrowthBook. Give them the url, then poll the Location." } ``` Give the person the `url`, then poll [`GET /v1/confirmations/{id}`](/api/meta/operation/getConfirmation). Pass `wait` (in seconds, up to 30) to hold the poll open until the status changes: ```bash theme={null} curl "https://api.growthbook.io/api/v1/confirmations/cnf_abc123?wait=30" \ -H "Authorization: Bearer secret_abc123DEF456" ``` | `status` | Meaning | | - | - | | `pending` | Waiting for a person, until `expiresAt` (an hour after the request) | | `running` | Confirmed, and running now | | `completed` | It ran. `response` holds the status and body the request returned, which may be an error | | `rejected` | A person declined it, and nothing ran | | `expired` | Nobody decided in time, or it acts on something that changed after it was held (with a `400` `response`). Nothing ran | `note` carries anything the person left for the agent when they decided. `actions` lists what the request was held for, each with the environments it touches. * Only the key that made the request, or another token of the same person, can read the confirmation. * Sending the same request again while it's pending returns the same confirmation. * A request that publishes, reverts or archives, or that changes a Feature Flag or an Experiment's status, schedule, targeting or traffic, isn't run if what it acts on changed after it was held. Other writes, such as editing settings or a draft, run as sent, so they can overwrite changes made while they waited. * A confirmed request runs once, with the key's permissions (or the member's role it assumes), not the confirmer's. Send it again to retry, which asks for a new confirmation. * Confirming isn't a review approval. ## Errors The API may return the following error status codes: * **400** - Bad Request - Often due to a missing required parameter * **401** - Unauthorized - No valid API key provided * **402** - Request Failed - The parameters are valid, but the request failed * **403** - Forbidden - Provided API key does not have the required access * **404** - Not Found - Unknown API route or requested resource * **409** - Conflict - The resource changed since it was read, or its state blocks the request, such as an experiment start's checklist. `code` names which. * **422** - Unprocessable Entity - The request is valid, but a warning, validation rule, approval requirement, or another publishing gate blocked it. Do not assume that `ignoreWarnings` clears every 422 response. * **429** - Too Many Requests - You exceeded the rate limit of 60 requests per minute, or the limit your self-hosted server sets. Try again later. * **5XX** - Server Error - Something went wrong on GrowthBook's end (these are rare) The response body will be a JSON object with the following properties: * **message** - Information about the error ### Publishing gates Automated callers, including AI agents, should report a blocked response to a person instead of retrying with an override. Use an override only when a person has explicitly asked for it. Publish and experiment start responses include a `gates` array that explains every blocker: * `type`, `severity`, and `messages` identify the problem. * `override` names the request-body field that can bypass it. This is `ignoreWarnings` for warnings, `skipSchemaValidation` for schema and invariant failures, `skipHooks` for Custom Hook rejections, or `skipChecklist` for an experiment start's launch checklist items. A value of `null` means there is no request-body override. * `requiresPermission` identifies any additional permission needed to use the override. * `resolution` provides an API action, method, and path when the blocker must be resolved another way. For example, an approval gate is cleared by approving the revision or by using a caller with **Bypass draft approvals** access. A Config lock is cleared through the unlock route in `resolution`. When a successful publish bypasses a gate, the response includes `bypassedGates`. Each entry reports the gate `type` and how it was bypassed in `via`, which is one of `ignoreWarnings`, `skipSchemaValidation`, `skipHooks`, `skipChecklist`, `bypassApprovalPermission`, `restApiBypassesReviews`, or `revertsBypassApproval` (reverts only). This field is omitted when no gates were bypassed. ### Request flags Every refusal a request can get past with a flag: | Flag | Where | Gets past | | - | - | - | | `ignoreWarnings` | Body | Warnings (`422`) | | `skipSchemaValidation` | Body | Schema and invariant failures, with **Bypass draft approvals** access | | `skipHooks` | Body | Custom Hook rejections | | `skipChecklist` | Body | An experiment start's incomplete launch checklist items (`409` `checklist_incomplete`), but not its blockers | | `allowUnscopedRules` | Body | A rule for All Environments and All Projects where the organization requires a scope (`422` `unscoped_rule`) | | `overrideDraftLimit=true` | Query string | Creating a draft past the draft limit | # Removes a single user from an organization Source: https://docs.growthbook.io/api/members/operation/deleteMember openapi.yaml DELETE /v1/members/{id} # Get all organization members Source: https://docs.growthbook.io/api/members/operation/listMembers openapi.yaml GET /v1/members # Update a member's role Source: https://docs.growthbook.io/api/members/operation/updateMemberRole openapi.yaml POST /v1/members/{id}/role Update a member's global role (including any environment restrictions, if applicable). Can also update a member's project roles if your plan supports it. # Get a confirmation Source: https://docs.growthbook.io/api/meta/operation/getConfirmation openapi.yaml GET /v1/confirmations/{id} Poll a held request until a person confirms or rejects it in GrowthBook. Pass `wait` (seconds, max 30) to hold the request open until the status changes. # Get the GrowthBook server version and build info Source: https://docs.growthbook.io/api/meta/operation/getVersion openapi.yaml GET /v1/version # Deletes a metric Source: https://docs.growthbook.io/api/metrics/operation/deleteMetric openapi.yaml DELETE /v1/metrics/{id} # Get a single metric Source: https://docs.growthbook.io/api/metrics/operation/getMetric openapi.yaml GET /v1/metrics/{id} # Get results for all experiments that use a metric Source: https://docs.growthbook.io/api/metrics/operation/listMetricExperiments openapi.yaml GET /v1/metrics/{id}/experiments Returns, for each experiment that uses the given metric (directly or via a metric group), the per-variation results for that metric from the latest snapshot. Supports the same filtering as the experiment list views via a raw search string or structured query params. Note: at most the 1000 most recent experiments using the metric are considered; filters and pagination are applied within that set, so results may be incomplete for metrics used by more than 1000 experiments. # Get all metrics Source: https://docs.growthbook.io/api/metrics/operation/listMetrics openapi.yaml GET /v1/metrics # Create a single metric Source: https://docs.growthbook.io/api/metrics/operation/postMetric openapi.yaml POST /v1/metrics # Update a metric Source: https://docs.growthbook.io/api/metrics/operation/putMetric openapi.yaml PUT /v1/metrics/{id} # Delete a namespace Source: https://docs.growthbook.io/api/namespaces/operation/deleteNamespace openapi.yaml DELETE /v1/namespaces/{id} Permanently removes a namespace from the organization. Returns a 409 error if any active experiments currently reference this namespace — disable or remove those references first. # Get a single namespace Source: https://docs.growthbook.io/api/namespaces/operation/getNamespace openapi.yaml GET /v1/namespaces/{id} # Get namespace membership Source: https://docs.growthbook.io/api/namespaces/operation/getNamespaceMemberships openapi.yaml GET /v1/namespaces/{id}/memberships # Get all namespaces Source: https://docs.growthbook.io/api/namespaces/operation/listNamespaces openapi.yaml GET /v1/namespaces # Create a namespace Source: https://docs.growthbook.io/api/namespaces/operation/postNamespace openapi.yaml POST /v1/namespaces # Rotate namespace seed Source: https://docs.growthbook.io/api/namespaces/operation/postNamespaceRotateSeed openapi.yaml POST /v1/namespaces/{id}/rotateSeed ⚠️ Dangerous: sets a new seed for a multiRange namespace. Every user's bucket position within the namespace is re-computed immediately, which re-randomizes traffic eligibility for **all** experiments currently using this namespace. Only do this if you intentionally want to reshuffle all allocations across experiments. This could be useful when re-using a namespace for a new set of experiments. # Update a namespace Source: https://docs.growthbook.io/api/namespaces/operation/putNamespace openapi.yaml PUT /v1/namespaces/{id} # Get all organizations (only for super admins on multi-org Enterprise Plan only) Source: https://docs.growthbook.io/api/organizations/operation/listOrganizations openapi.yaml GET /v1/organizations # Create a single organization (only for super admins on multi-org Enterprise Plan only) Source: https://docs.growthbook.io/api/organizations/operation/postOrganization openapi.yaml POST /v1/organizations # Edit a single organization (only for super admins on multi-org Enterprise Plan only) Source: https://docs.growthbook.io/api/organizations/operation/putOrganization openapi.yaml PUT /v1/organizations/{id} # Deletes a single project Source: https://docs.growthbook.io/api/projects/operation/deleteProject openapi.yaml DELETE /v1/projects/{id} # Get a single project Source: https://docs.growthbook.io/api/projects/operation/getProject openapi.yaml GET /v1/projects/{id} # Get all projects Source: https://docs.growthbook.io/api/projects/operation/listProjects openapi.yaml GET /v1/projects # Create a single project Source: https://docs.growthbook.io/api/projects/operation/postProject openapi.yaml POST /v1/projects # Edit a single project Source: https://docs.growthbook.io/api/projects/operation/putProject openapi.yaml PUT /v1/projects/{id} # Get a single query Source: https://docs.growthbook.io/api/queries/operation/getQuery openapi.yaml GET /v1/queries/{id} # Add a target rule to a ramp schedule Source: https://docs.growthbook.io/api/ramp-schedules/operation/addTargetRampSchedule openapi.yaml POST /v1/ramp-schedules/{id}/actions/add-target Attaches an additional feature rule to this ramp schedule. The `ruleId` must identify a rule that is already published and must not already be controlled by another schedule. `environment` is accepted for backward compatibility with pre-v2 ramps but is deprecated and no longer required. This skips the revision review flow, so when the organization requires review anywhere it is limited to credentials that may bypass approval. The reviewed way to attach a plan is `PUT /features/{id}/revisions/{version}/rules/{ruleId}/ramp-schedule` followed by a publish. # Advance to the next step, overriding any holds Source: https://docs.growthbook.io/api/ramp-schedules/operation/apiAdvanceRampSchedule openapi.yaml POST /v1/ramp-schedules/{id}/actions/advance Moves the schedule to the next step, bypassing **all** hold conditions — interval, min sample size, and monitoring signal holds. Accepts `running` or `paused` status; if paused, the schedule is implicitly resumed (timing anchors recalculated) before the step moves. **Approval gate**: if the current step has an unsatisfied `holdConditions.requiresApproval` gate, this endpoint returns **409** by default. Either call `/actions/approve-step` first (recommended), or pass `force: true` to override the approval gate. `force: true` requires `FlagsBypassApprovals` permission and is logged in the audit trail. **Two common uses:** - **Post-interval monitoring hold** (`decision: "hold"`, interval elapsed): the step timer has completed but a signal or guardrail is flagging concern. Use this after reviewing the `/status` health summary and deciding to accept the risk and proceed. - **Hard override**: skip a step regardless of where it is in its interval or hold conditions (CI gate, external deployment pipeline). When to use other actions instead: - **`/actions/resume`** — restores a paused schedule without moving the step. - **`/actions/approve-step`** — clears only the approval gate; other conditions still resolve naturally. - **`/actions/rollback`** — preferred response when `decision: "rollback"` or signals include `guardrail-failing`. # Approve the pending approval gate Source: https://docs.growthbook.io/api/ramp-schedules/operation/approveStepRampSchedule openapi.yaml POST /v1/ramp-schedules/{id}/actions/approve-step Clears whichever approval gate is currently pending on the schedule: - **Start gate** — a schedule created with `requiresStartApproval` sits in `ready` at step -1 with its rule disabled (zero traffic). Approving starts the ramp (or, if a future `startDate` is set, arms it to start on that date). - **Step gate** — the `holdConditions.requiresApproval` gate on the current step of a `running` schedule. For a step gate, approval is the **final** gate: it can only be granted once every other hold has cleared. This endpoint rejects the request (`400`) if the step is not yet ready — while the interval timer is still counting down, or (for monitored steps) before fresh analysis is available or while a guardrail/health signal is failing. Poll `/status` and call this once it reports awaiting approval. **Non-monitored steps**: once the interval has elapsed, approving clears the last hold and advances immediately, chaining through subsequent instant steps. **Monitored steps**: approving clears the last hold and the agenda advances on its next tick (re-checking analysis first). Different from `/actions/advance`: `approve-step` works within the normal evaluation flow and refuses to skip ahead of the interval or any other unmet gate. Use `/actions/advance` to bypass all remaining holds. Requires update + publish (start gate) or review (step gate) permissions for the associated feature. # Complete a ramp schedule immediately Source: https://docs.growthbook.io/api/ramp-schedules/operation/completeRampSchedule openapi.yaml POST /v1/ramp-schedules/{id}/actions/complete Immediately applies the schedule's end-state rule patches (the equivalent of what would happen after the last step advances normally) and marks the schedule as `completed`, skipping any remaining steps. Pass `disableRule: true` to also disable the linked rule (equivalent to the cutoff-date-driven completion). # Delete a single rampSchedule Source: https://docs.growthbook.io/api/ramp-schedules/operation/deleteRampSchedule openapi.yaml DELETE /v1/ramp-schedules/{id} Permanently deletes a ramp schedule. This does not undo any rule patches that were already applied by completed steps. # Remove a target rule from a ramp schedule Source: https://docs.growthbook.io/api/ramp-schedules/operation/ejectTargetRampSchedule openapi.yaml POST /v1/ramp-schedules/{id}/actions/eject-target Detaches a target rule from this ramp schedule. Identify the target either by its `targetId` or by the `[ruleId, environment]` pair. If this is the last target on the schedule, the schedule is deleted entirely and the response contains `deleted: true` instead of `rampSchedule`. # Get a single rampSchedule Source: https://docs.growthbook.io/api/ramp-schedules/operation/getRampSchedule openapi.yaml GET /v1/ramp-schedules/{id} # Get ramp schedule status summary Source: https://docs.growthbook.io/api/ramp-schedules/operation/getRampScheduleStatus openapi.yaml GET /v1/ramp-schedules/{id}/status Returns a real-time status summary for a ramp schedule: current step, overall health decision, traffic quality, and per-metric effect sizes. Designed for CI pipeline integrations and monitoring dashboards that need a single call to determine whether it is safe to advance. # Jump to a specific step Source: https://docs.growthbook.io/api/ramp-schedules/operation/jumpRampSchedule openapi.yaml POST /v1/ramp-schedules/{id}/actions/jump Teleports the schedule to `targetStepIndex` (forward or backward) and leaves it `paused`. Resets timing anchors so the destination step's interval starts fresh when the schedule is next resumed or started. Pass `-1` to return to the pre-start position without applying rollback rule patches — useful for resetting a non-started schedule. For a full traffic revert, use `/actions/rollback` instead. Accepts any non-terminal schedule status. # Get all rampSchedules Source: https://docs.growthbook.io/api/ramp-schedules/operation/listRampSchedules openapi.yaml GET /v1/ramp-schedules Returns all ramp schedules for the organization, with optional filters. # Pause matching ramp schedules Source: https://docs.growthbook.io/api/ramp-schedules/operation/pauseMatchingRampSchedules openapi.yaml POST /v1/ramp-schedules/actions/pause Pauses every `running` ramp schedule that matches the query filters. Built for an alerting tool's webhook, such as a Datadog monitor. Each schedule stays paused until someone calls `/actions/resume` on it. Each filter takes comma-separated values and matches any of them; different filters must all match. With no filters, every running schedule is paused, for example during a site-wide outage. Returns each matched schedule and whether the pause was applied or skipped, for example because the API key can't publish its Feature Flag. Unknown body fields are ignored, so a tool can send its own payload. # Pause a ramp schedule Source: https://docs.growthbook.io/api/ramp-schedules/operation/pauseRampSchedule openapi.yaml POST /v1/ramp-schedules/{id}/actions/pause Pauses a `running` schedule. Traffic percentages are frozen at their current values; no step advancement happens while paused. Records `pausedAt` so that interval timing can be correctly offset when the schedule resumes. Use `/actions/resume` to continue from the same step, or `/actions/rollback` to revert all rule effects entirely. # Create a ramp schedule Source: https://docs.growthbook.io/api/ramp-schedules/operation/postRampSchedule openapi.yaml POST /v1/ramp-schedules Creates a ramp schedule, optionally attached to a published feature rule by passing `featureId` and `ruleId` together (the target is then injected into every action). Attaching on creation skips the revision review flow, so when the organization requires review anywhere it is limited to credentials that may bypass approval. The reviewed way to attach a plan is `PUT /features/{id}/revisions/{version}/rules/{ruleId}/ramp-schedule` followed by a publish. Without a target the schedule is a free-standing skeleton in `pending` status. Requires a Pro plan or above. # Trigger a manual monitoring update Source: https://docs.growthbook.io/api/ramp-schedules/operation/refreshMonitoringRampSchedule openapi.yaml POST /v1/ramp-schedules/{id}/actions/refresh-monitoring Queues a new analysis snapshot for the schedule's monitoring experiment. The snapshot runs asynchronously — poll `GET /ramp-schedules/:id/status` until `snapshotAt` advances to confirm results are ready. Only available when the schedule is within its monitored step window: - Not in a terminal state (`completed` or `rolled-back`). - Has at least one step with `monitored: true`. - `currentStepIndex` is within `[firstMonitoredStepIndex, lastMonitoredStepIndex]`. Violating any condition returns **409 Conflict** with a descriptive message. Requires the `runQueries` permission on the configured datasource (enforced via `canRunExperimentQueries`). # Restart a terminal ramp schedule Source: https://docs.growthbook.io/api/ramp-schedules/operation/restartRampSchedule openapi.yaml POST /v1/ramp-schedules/{id}/actions/restart Brings a `rolled-back` (or `completed`) schedule back into `running` in a single call. Any prior start-on-date delays are cleared (`startedAt`, `phaseStartedAt`, etc. are reset), `currentStepIndex` is normalised to `-1`, then the same logic as `/actions/start` runs to apply start actions and advance through immediately-eligible steps. The rollback that preceded this already rewound rule effects to the starting position; this endpoint does **not** re-execute that rewind for `rolled-back` schedules. `completed` schedules are defensively rewound first. # Resume a paused ramp schedule Source: https://docs.growthbook.io/api/ramp-schedules/operation/resumeRampSchedule openapi.yaml POST /v1/ramp-schedules/{id}/actions/resume Resumes a `paused` schedule without moving the current step. Timing anchors (`phaseStartedAt`, `startedAt`) are shifted forward by the pause duration so that interval-based steps continue from where they left off rather than restarting their clock. Does **not** advance to the next step — use `/actions/advance` if you also want to skip the remainder of the current step. # Roll back matching ramp schedules Source: https://docs.growthbook.io/api/ramp-schedules/operation/rollbackMatchingRampSchedules openapi.yaml POST /v1/ramp-schedules/actions/rollback Rolls back every `running` or `paused` ramp schedule that matches the query filters. Built for an alerting tool's webhook, such as a Datadog monitor. Each filter takes comma-separated values and matches any of them; different filters must all match. At least one filter is required. Returns each matched schedule and whether the rollback was applied or skipped, for example because the API key can't publish its Feature Flag. Unknown body fields are ignored, so a tool can send its own payload. # Roll back a ramp schedule Source: https://docs.growthbook.io/api/ramp-schedules/operation/rollbackRampSchedule openapi.yaml POST /v1/ramp-schedules/{id}/actions/rollback Rewinds all ramp effects (rule coverage, targeting, etc.) to the starting position and lands in terminal `rolled-back` status. The reason is persisted as `lastRollbackReason` (prefixed with `Manual: `) and surfaced in the UI. This is also the correct response to a monitoring alert — when the `/status` endpoint returns `decision: "rollback"` or signals include `guardrail-failing`, call this endpoint with a descriptive `reason`. From this terminal state the schedule can be brought back to `ready` via `/actions/restart`, after which `/actions/start` will run it again. # Toggle automatic monitoring updates Source: https://docs.growthbook.io/api/ramp-schedules/operation/setAutoUpdateRampSchedule openapi.yaml POST /v1/ramp-schedules/{id}/actions/set-auto-update Deprecated alias for setting monitoring mode. Prefer `/actions/set-monitoring-mode`. # Set ramp monitoring mode Source: https://docs.growthbook.io/api/ramp-schedules/operation/setMonitoringModeRampSchedule openapi.yaml POST /v1/ramp-schedules/{id}/actions/set-monitoring-mode Sets the user preference for ramp monitoring updates. In `manual` mode, automatic snapshot scheduling is disabled and operators must click Update manually. In `auto` mode, snapshots run automatically when the current step is monitored and the ramp is running. # Start a ramp schedule Source: https://docs.growthbook.io/api/ramp-schedules/operation/startRampSchedule openapi.yaml POST /v1/ramp-schedules/{id}/actions/start Transitions the schedule from `ready` to `running`. The schedule must have at least one target rule attached — a schedule created without targets starts in `pending` and moves to `ready` automatically when the first target is attached via `/actions/add-target`. The first step is processed immediately: interval-free steps advance right away; interval-based steps arm a timer. Once started, use `/actions/pause` to halt, `/actions/advance` to skip steps, or `/actions/rollback` to revert. # Update a single rampSchedule Source: https://docs.growthbook.io/api/ramp-schedules/operation/updateRampSchedule openapi.yaml PUT /v1/ramp-schedules/{id} Updates the name, steps, endActions, startDate, or cutoffDate of a ramp schedule. Only allowed when the schedule is in `pending`, `ready`, or `paused` status. Changing `steps`, `startActions`, `endActions`, `startDate`, or `cutoffDate` on a schedule that is attached to a rule skips the revision review flow, so when the organization requires review anywhere it is limited to credentials that may bypass approval. Otherwise stage a new plan on a draft revision and publish it. **targetId shorthand**: When providing `steps` or `endActions`, you may omit `targetId` (or pass `"t1"`) in each action. If the schedule has exactly one active target, the server will resolve it automatically. For schedules with multiple targets, provide the explicit target UUID from `targets[].id`. **Coverage on monitored steps**: See the create endpoint description for details on how `coverage` is interpreted for monitored steps (total enrollment, not variation-1 exposure). # Update ramp lockdown configuration Source: https://docs.growthbook.io/api/ramp-schedules/operation/updateRampScheduleLockdown openapi.yaml PUT /v1/ramp-schedules/{id}/lockdown Sets the lockdown mode. `locked` prevents other users from publishing unrelated changes to the parent feature while the ramp is running — useful when you want to ensure no external edits interfere with a live rollout. It does **not** affect the ramp's own auto-advancement or monitoring behavior; use `actions/pause` to halt the ramp itself. `none` removes the publishing restriction. # Update ramp monitoring configuration Source: https://docs.growthbook.io/api/ramp-schedules/operation/updateRampScheduleMonitoring openapi.yaml PUT /v1/ramp-schedules/{id}/monitoring Replaces the monitoring configuration. Health-action thresholds (`srmAction`, `noTrafficAction`, etc.) can be updated at any time. Once a linked SafeRollout has started, `datasourceId`, `exposureQueryId`, the metric IDs and the snapshot cadence are all locked — stop and recreate the schedule to change the data source. Changes to guardrail or signal metric IDs take effect on the next analysis run. # Update ramp schedule steps Source: https://docs.growthbook.io/api/ramp-schedules/operation/updateRampScheduleSteps openapi.yaml PUT /v1/ramp-schedules/{id}/steps Fully replaces the steps array for a ramp schedule. Only allowed when the schedule is in a non-running, non-terminal state (`ready`, `pending`, or `paused`). Pause a running schedule first; restart a terminal schedule first. On a schedule attached to a rule this skips the revision review flow, so when the organization requires review anywhere it is limited to credentials that may bypass approval. **Step actions** (coverage/targeting patches) are not accepted here — they change the SDK payload and must go through a feature revision draft. Existing step actions are preserved for each position. Use `PUT /v2/features/:id/revisions/:version/rules/:ruleId/ramp-schedule` to modify coverage/targeting. # Atomically publish revisions across multiple entities Source: https://docs.growthbook.io/api/releases/operation/postReleasePublishRevisions openapi.yaml POST /v1/releases/publish-revisions Publishes a set of revisions — at most one per entity — across Feature Flags, Saved Groups, configs, and constants as a single all-or-nothing operation. Validation, guards, and custom hooks run against the combined end-state of the whole set, so interdependent changes (e.g. a config schema change plus the values that depend on it) publish together even when the in-between states would be invalid. A blocked publish returns one 422 listing every gate across every item and the flag that clears each. A concurrent change to any target aborts with a 409 and nothing publishes. A failure after the commit starts rolls everything back and emits `revision.publishFailed` for each revision in the set. SDK payloads refresh once per request. Pass `dryRun: true` for the full gate report with zero writes. Requires the `releases` commercial feature. # Get a single report Source: https://docs.growthbook.io/api/reports/operation/getReport openapi.yaml GET /v1/reports/{id} # Get all reports Source: https://docs.growthbook.io/api/reports/operation/listReports openapi.yaml GET /v1/reports # Create a new report Source: https://docs.growthbook.io/api/reports/operation/postReport openapi.yaml POST /v1/reports # Refresh a report by re-running its analysis Source: https://docs.growthbook.io/api/reports/operation/postReportRefresh openapi.yaml POST /v1/reports/{id}/refresh # Update report metadata (title, description, visibility) Source: https://docs.growthbook.io/api/reports/operation/putReportMetadata openapi.yaml PUT /v1/reports/{id}/metadata # Update report analysis settings Source: https://docs.growthbook.io/api/reports/operation/putReportSettings openapi.yaml PUT /v1/reports/{id}/settings Updates the analysis settings for an existing report. Changes are staged and do not take effect until you call `POST /reports/:id/refresh`. # Get a single saved group revision Source: https://docs.growthbook.io/api/saved-group-revisions/operation/getSavedGroupRevision openapi.yaml GET /v1/saved-groups-revisions/{savedGroupId}/{version} Returns the revision at the specified version for this saved group. Use `GET /saved-groups-revisions/{savedGroupId}/latest` for the most recent active draft. # Get the most recent active draft revision Source: https://docs.growthbook.io/api/saved-group-revisions/operation/getSavedGroupRevisionLatest openapi.yaml GET /v1/saved-groups-revisions/{savedGroupId}/latest Returns the most recently updated open (non-merged, non-discarded) revision for the saved group. Returns 404 if there is no active draft. Pass `mine=true` to restrict to drafts authored by the calling user. Needs a personal access token, or an organization API key that assumes a named member's role. # Get merge status for a draft revision Source: https://docs.growthbook.io/api/saved-group-revisions/operation/getSavedGroupRevisionMergeStatus openapi.yaml GET /v1/saved-groups-revisions/{savedGroupId}/{version}/merge-status Runs a dry-run merge of the draft against the current live saved group and returns any conflicts. Use this before publishing to preview changes and detect conflicting edits. # List revisions for a saved group Source: https://docs.growthbook.io/api/saved-group-revisions/operation/getSavedGroupRevisions openapi.yaml GET /v1/saved-groups-revisions/{savedGroupId} Returns a paginated list of revisions for this saved group, sorted newest-first. Optionally filtered by status, author, or the calling user's involvement. # List saved-group revisions across the organization Source: https://docs.growthbook.io/api/saved-group-revisions/operation/listSavedGroupRevisions openapi.yaml GET /v1/saved-groups-revisions Returns a paginated list of revisions across all saved groups in the organization, sorted newest-first. Optionally filtered by saved group, status, author, or the calling user's involvement. # Create a draft revision Source: https://docs.growthbook.io/api/saved-group-revisions/operation/postSavedGroupRevision openapi.yaml POST /v1/saved-groups-revisions/{savedGroupId} Creates a new draft revision branched from the current live saved group. A saved group can have multiple concurrent drafts; use this to start an isolated line of edits. # Discard a draft revision Source: https://docs.growthbook.io/api/saved-group-revisions/operation/postSavedGroupRevisionDiscard openapi.yaml POST /v1/saved-groups-revisions/{savedGroupId}/{version}/discard Permanently discards a draft revision. Only open revisions (not merged or already-discarded) can be discarded. # Append items to a list saved group draft revision Source: https://docs.growthbook.io/api/saved-group-revisions/operation/postSavedGroupRevisionItemsAdd openapi.yaml POST /v1/saved-groups-revisions/{savedGroupId}/{version}/items/add Appends the provided items (deduplicated) to the draft's `values` array. Only valid for `list` saved groups. Pass `version: "new"` to auto-create a draft. Duplicate items are merged on top of any existing draft, so multiple successive add/remove calls accumulate. # Remove items from a list saved group draft revision Source: https://docs.growthbook.io/api/saved-group-revisions/operation/postSavedGroupRevisionItemsRemove openapi.yaml POST /v1/saved-groups-revisions/{savedGroupId}/{version}/items/remove Removes the provided items from the draft's `values` array. Only valid for `list` saved groups. Pass `version: "new"` to auto-create a draft. # Publish a draft revision Source: https://docs.growthbook.io/api/saved-group-revisions/operation/postSavedGroupRevisionPublish openapi.yaml POST /v1/saved-groups-revisions/{savedGroupId}/{version}/publish Publishes the draft and applies its changes to the live Saved Group. The caller needs Publish access in every assigned Project. When approval is required, the draft must be approved unless the caller has Bypass draft approvals access. If the organization requires rebasing, an out-of-date draft must be rebased first; an authorized caller can instead send `ignoreWarnings: true` to force-publish it. A 422 response lists every blocking gate and the available resolution. # Rebase a draft revision onto the current live saved group Source: https://docs.growthbook.io/api/saved-group-revisions/operation/postSavedGroupRevisionRebase openapi.yaml POST /v1/saved-groups-revisions/{savedGroupId}/{version}/rebase Updates the draft's base snapshot to the current live state, applying the draft's changes on top. Supply `conflictResolutions` to resolve any conflicting fields. Strategies are `overwrite` (use the draft's value), `discard` (keep the live value), or `union` (merge arrays — use only on `values`). Optimistic locking is not enforced by this endpoint; callers who need strict locking should call `merge-status` before and after. # Recall a review request Source: https://docs.growthbook.io/api/saved-group-revisions/operation/postSavedGroupRevisionRecallReview openapi.yaml POST /v1/saved-groups-revisions/{savedGroupId}/{version}/recall-review Pulls a revision in review (`pending-review`, `changes-requested`, or `approved`) back to `draft`, clearing existing reviews and disarming any auto-publish-on-approval. # Reopen a discarded revision Source: https://docs.growthbook.io/api/saved-group-revisions/operation/postSavedGroupRevisionReopen openapi.yaml POST /v1/saved-groups-revisions/{savedGroupId}/{version}/reopen Returns a previously discarded revision to `draft` status so it can be edited and published again. Only discarded revisions can be reopened. # Request review for a draft revision Source: https://docs.growthbook.io/api/saved-group-revisions/operation/postSavedGroupRevisionRequestReview openapi.yaml POST /v1/saved-groups-revisions/{savedGroupId}/{version}/request-review Moves the draft from `draft` into `pending-review`. Notifies reviewers per the org's approval-flow settings. Set `autoPublishOnApproval` to `true` to publish the revision automatically the moment it is approved (GitHub auto-merge model). This requires the org to have auto-publish-on-approval enabled and the caller to have publish permission on the saved group; the auto-publish then executes with the caller's authority. # Revert the saved group to a prior revision Source: https://docs.growthbook.io/api/saved-group-revisions/operation/postSavedGroupRevisionRevert openapi.yaml POST /v1/saved-groups-revisions/{savedGroupId}/{version}/revert Creates a new draft (or immediately publishes) whose content matches the specified historical revision. Defaults to creating a draft; when the org enables 'reverts bypass approval' it defaults to publishing immediately. Pass `strategy` to override. # Schedule (or cancel) a deferred publish Source: https://docs.growthbook.io/api/saved-group-revisions/operation/postSavedGroupRevisionSchedulePublish openapi.yaml POST /v1/saved-groups-revisions/{savedGroupId}/{version}/schedule-publish Arms a revision to publish automatically at a future time. Pass `scheduledPublishAt` as an RFC3339 timestamp in the future to arm, or `null` to cancel a pending schedule. Requires the `scheduled-revisions` commercial feature and publish permission on the Saved Group. A draft that still requires approval must request review first (or be armed with `bypassApproval` by a caller who can bypass). # Submit a review on a draft revision Source: https://docs.growthbook.io/api/saved-group-revisions/operation/postSavedGroupRevisionSubmitReview openapi.yaml POST /v1/saved-groups-revisions/{savedGroupId}/{version}/submit-review Submits an `approve`, `request-changes`, or `comment` review on the revision. The verdict belongs to whoever the request acts as; see [Naming who asked](/api/introduction#naming-who-asked). No one can rule on a draft they created. Submitting `approve` or `request-changes` needs Review access. A `comment` is participation rather than a verdict, so it is also open to the Comments permission or draft authority on the entity. Authors and contributors cannot submit `approve` reviews on their own drafts when the org has `blockSelfApproval` enabled. When `decision` is `approve` and the revision has `autoPublishOnApproval` enabled, the revision is automatically published after approval. The response includes `autoPublished: true` when this happens. Pass `skipAutoPublish: true` to approve without triggering auto-publish. # Retract your own review verdict Source: https://docs.growthbook.io/api/saved-group-revisions/operation/postSavedGroupRevisionUndoReview openapi.yaml POST /v1/saved-groups-revisions/{savedGroupId}/{version}/undo-review Retracts the calling user's own active `approve` or `request-changes` verdict, returning the revision to `pending-review`. Review comments stay in the log. Retracting a `request-changes` can leave the revision approved by someone else, in which case an armed auto-publish fires. The verdict belongs to whoever the request acts as; see [Naming who asked](/api/introduction#naming-who-asked). No one can rule on a draft they created. # Stage an archive/unarchive in a draft revision Source: https://docs.growthbook.io/api/saved-group-revisions/operation/putSavedGroupRevisionArchive openapi.yaml PUT /v1/saved-groups-revisions/{savedGroupId}/{version}/archive Stages an archive or unarchive on the draft. Pass `version: "new"` to auto-create a draft. Archived saved groups can be permanently deleted via `DELETE /saved-groups/{id}` once the archive is published. # Update the condition of a condition saved group draft revision Source: https://docs.growthbook.io/api/saved-group-revisions/operation/putSavedGroupRevisionCondition openapi.yaml PUT /v1/saved-groups-revisions/{savedGroupId}/{version}/condition Stages a new JSON-encoded condition for the draft. Only valid for `condition` saved groups. Pass `version: "new"` to auto-create a draft. # Update saved group metadata in a draft revision Source: https://docs.growthbook.io/api/saved-group-revisions/operation/putSavedGroupRevisionMetadata openapi.yaml PUT /v1/saved-groups-revisions/{savedGroupId}/{version}/metadata Stages metadata changes (name, owner, description, projects) on the draft. Pass `version: "new"` to auto-create a draft. The change is only applied to the live saved group when the revision is merged. # Set the upload a remote saved group draft revision uses Source: https://docs.growthbook.io/api/saved-group-revisions/operation/putSavedGroupRevisionUploadVersion openapi.yaml PUT /v1/saved-groups-revisions/{savedGroupId}/{version}/upload-version Stages which upload the group uses. Only valid for `remote` saved groups, and the upload must be valid. Pass `version: "new"` to auto-create a draft. # Replace the values list in a list saved group draft revision Source: https://docs.growthbook.io/api/saved-group-revisions/operation/putSavedGroupRevisionValues openapi.yaml PUT /v1/saved-groups-revisions/{savedGroupId}/{version}/values Replaces the entire `values` array atomically. Only valid for `list` saved groups. For safe concurrent updates against a draft, prefer `POST .../items/add` and `POST .../items/remove`. Pass `version: "new"` to auto-create a draft. # Archive a single saved group Source: https://docs.growthbook.io/api/saved-groups/operation/archiveSavedGroup openapi.yaml POST /v1/saved-groups/{id}/archive Archives a Saved Group. If it is still referenced by a Feature Flag, experiment, or another Saved Group, the API returns 422 with the affected references. Send `"ignoreWarnings": true` to acknowledge those references and continue. When approval is required, create and publish an archive revision instead, or use a caller with Bypass draft approvals access. A successful response lists any skipped gates in `bypassedGates`. # Deletes a single saved group Source: https://docs.growthbook.io/api/saved-groups/operation/deleteSavedGroup openapi.yaml DELETE /v1/saved-groups/{id} # Delete an upload of a remote saved group Source: https://docs.growthbook.io/api/saved-groups/operation/deleteSavedGroupUpload openapi.yaml DELETE /v1/saved-groups/{id}/uploads/{version} Deletes the upload and its file. The version the group uses, or one an open draft uses, can't be deleted. Version numbers aren't reused. Needs permission to publish the saved group. # Get a single saved group Source: https://docs.growthbook.io/api/saved-groups/operation/getSavedGroup openapi.yaml GET /v1/saved-groups/{id} # Get features, experiments, and saved groups that reference this saved group Source: https://docs.growthbook.io/api/saved-groups/operation/getSavedGroupReferences openapi.yaml GET /v1/saved-groups/{id}/references # Get one upload of a remote saved group Source: https://docs.growthbook.io/api/saved-groups/operation/getSavedGroupUpload openapi.yaml GET /v1/saved-groups/{id}/uploads/{version} Returns an upload of any status. After creating an upload, poll this until `status.type` is `valid` or `invalid`. # List the uploads of a remote saved group Source: https://docs.growthbook.io/api/saved-groups/operation/listSavedGroupUploads openapi.yaml GET /v1/saved-groups/{id}/uploads Newest first. # Get all saved group Source: https://docs.growthbook.io/api/saved-groups/operation/listSavedGroups openapi.yaml GET /v1/saved-groups # Create a single saved group Source: https://docs.growthbook.io/api/saved-groups/operation/postSavedGroup openapi.yaml POST /v1/saved-groups # Upload a new version of a remote saved group's IDs Source: https://docs.growthbook.io/api/saved-groups/operation/postSavedGroupUpload openapi.yaml POST /v1/saved-groups/{id}/uploads Records the next version of the group's IDs. The start and end of the file are checked for one ID per line, then the whole file in the background; loaders trim and deduplicate the IDs. Poll `GET /saved-groups/{id}/uploads/{version}` until it's valid, then set the group's `currentUploadVersion` with `PUT /saved-groups-revisions/{id}/{version}/upload-version` to use it. Loaders pick up the group's version from `currentUpload` and download it from `GET /saved-groups/{id}/uploads/{version}/file`, which redirects to a short-lived signed storage URL. # Report loading a remote saved group upload into a data store Source: https://docs.growthbook.io/api/saved-groups/operation/postSavedGroupUploadLoad openapi.yaml POST /v1/saved-groups/{id}/uploads/{version}/loads Called by remote saved group loaders. Each report replaces the previous one from the same `storeId`. Needs permission to publish the saved group. # Get a signed URL to upload a remote saved group CSV Source: https://docs.growthbook.io/api/saved-groups/operation/postSavedGroupUploadUrl openapi.yaml POST /v1/saved-groups/{id}/upload-url Upload a CSV with one ID per line to the returned URL with `Content-Type: text/csv`, then create an upload with the `fileKey`. A first line equal to the attribute name is a header. Needs S3 or Google Cloud Storage. # Unarchive a single saved group Source: https://docs.growthbook.io/api/saved-groups/operation/unarchiveSavedGroup openapi.yaml POST /v1/saved-groups/{id}/unarchive Unarchives a Saved Group. When approval is required, create and publish an unarchive revision instead, or use a caller with Bypass draft approvals access. A successful response lists any skipped gates in `bypassedGates`. # Partially update a single saved group Source: https://docs.growthbook.io/api/saved-groups/operation/updateSavedGroup openapi.yaml POST /v1/saved-groups/{id} Applies the change immediately and records it as a published revision, so it appears in history and fires revision webhooks. When the organization requires approvals, open a draft instead or pass `bypassApproval` with the bypass permission. # Deletes a single SDK connection Source: https://docs.growthbook.io/api/sdk-connections/operation/deleteSdkConnection openapi.yaml DELETE /v1/sdk-connections/{id} # Get a single sdk connection Source: https://docs.growthbook.io/api/sdk-connections/operation/getSdkConnection openapi.yaml GET /v1/sdk-connections/{id} # Get all sdk connections Source: https://docs.growthbook.io/api/sdk-connections/operation/listSdkConnections openapi.yaml GET /v1/sdk-connections # Find a single sdk connection by its key Source: https://docs.growthbook.io/api/sdk-connections/operation/lookupSdkConnectionByKey openapi.yaml GET /v1/sdk-connections/lookup/{key} # Create a single sdk connection Source: https://docs.growthbook.io/api/sdk-connections/operation/postSdkConnection openapi.yaml POST /v1/sdk-connections # Update a single sdk connection Source: https://docs.growthbook.io/api/sdk-connections/operation/putSdkConnection openapi.yaml PUT /v1/sdk-connections/{id} # Deletes a single segment Source: https://docs.growthbook.io/api/segments/operation/deleteSegment openapi.yaml DELETE /v1/segments/{id} # Get a single segment Source: https://docs.growthbook.io/api/segments/operation/getSegment openapi.yaml GET /v1/segments/{id} # Get all segments Source: https://docs.growthbook.io/api/segments/operation/listSegments openapi.yaml GET /v1/segments # Create a single segment Source: https://docs.growthbook.io/api/segments/operation/postSegment openapi.yaml POST /v1/segments # Update a single segment Source: https://docs.growthbook.io/api/segments/operation/updateSegment openapi.yaml POST /v1/segments/{id} # Get organization settings Source: https://docs.growthbook.io/api/settings/operation/getSettings openapi.yaml GET /v1/settings # Update approval settings Source: https://docs.growthbook.io/api/settings/operation/putApprovalSettings openapi.yaml PUT /v1/settings/approvals Replace the approval requirements for feature flags, configs and constants, for saved groups, and the Targeting Projects review mode. Each family is replaced wholesale when supplied; omit one to leave it unchanged. # Get an experiment snapshot status Source: https://docs.growthbook.io/api/snapshots/operation/getExperimentSnapshot openapi.yaml GET /v1/snapshots/{id} # Get metric usage across experiments Source: https://docs.growthbook.io/api/usage/operation/getMetricUsage openapi.yaml GET /v1/usage/metrics Returns usage information for one or more legacy or fact metrics, showing which experiments use each metric and some usage statistics. If a metric is part of a metric group, then usage of that metric group counts as usage of all metrics in the group. Warning: only includes experiments that you have access to! If you do not have admin access or read access to experiments across all projects, this endpoint may not return the latest usage data across all experiments. # Get a single visual changeset Source: https://docs.growthbook.io/api/visual-changesets/operation/getVisualChangeset openapi.yaml GET /v1/visual-changesets/{id} # Get all visual changesets Source: https://docs.growthbook.io/api/visual-changesets/operation/listVisualChangesets openapi.yaml GET /v1/experiments/{id}/visual-changesets # Create a visual change for a visual changeset Source: https://docs.growthbook.io/api/visual-changesets/operation/postVisualChange openapi.yaml POST /v1/visual-changesets/{id}/visual-change # Create a visual changeset for an experiment Source: https://docs.growthbook.io/api/visual-changesets/operation/postVisualChangesets openapi.yaml POST /v1/experiments/{id}/visual-changesets # Update a visual change for a visual changeset Source: https://docs.growthbook.io/api/visual-changesets/operation/putVisualChange openapi.yaml PUT /v1/visual-changesets/{id}/visual-change/{visualChangeId} # Update a visual changeset Source: https://docs.growthbook.io/api/visual-changesets/operation/putVisualChangeset openapi.yaml PUT /v1/visual-changesets/{id} # API Source: https://docs.growthbook.io/app/api Use the GrowthBook REST API to manage the app, plus public SDK Connection endpoints that SDKs use to fetch feature flags. GrowthBook offers a full REST API for interacting with the application. [View REST API Docs](/api/introduction) Two key settings help when an agent or script makes changes: the `X-GrowthBook-Requested-By` header names the member who asked for each one, and **Require confirmation** holds risky changes until a person confirms them in GrowthBook. See [API Keys for Agents](/integrations/ai-agents/api-keys). ## Common Tasks These endpoints cover the most common automation tasks: **Experiments** * [Create an experiment](/api/experiments/operation/postExperiment) (`POST /v1/experiments`) or [list existing experiments](/api/experiments/operation/listExperiments) (`GET /v1/experiments`). * [Get results for an experiment](/api/experiments/operation/getExperimentResults) (`GET /v1/experiments/{id}/results`) to pull the latest analysis into your own reporting. **Feature flags** * [Get a single feature](/api/features-v2/operation/getFeatureV2) (`GET /v2/features/{id}`), including its rules in every environment. * [Toggle a feature on or off](/api/features-v2/operation/toggleFeatureV2) (`POST /v2/features/{id}/toggle`) in one or more environments, for example from a deploy script or an incident runbook. **Metrics and data** * [Create a fact metric](/api/fact-metrics/operation/postFactMetric) (`POST /v1/fact-metrics`) or [list fact metrics](/api/fact-metrics/operation/listFactMetrics) (`GET /v1/fact-metrics`). * [Get a fact table](/api/fact-tables/operation/getFactTable) (`GET /v1/fact-tables/{id}`) to inspect its SQL, columns, and filters. * [List data sources](/api/data-sources/operation/listDataSources) (`GET /v1/data-sources`) to find the ids other endpoints ask for. **Targeting** * [Create a saved group](/api/saved-groups/operation/postSavedGroup) (`POST /v1/saved-groups`) to reuse a list of ids or a targeting condition across features and experiments. * [Create a segment](/api/segments/operation/postSegment) (`POST /v1/segments`) to analyze experiment results for a subset of users. ## SDK Connection Endpoints In addition to the REST API above, there is one additional readonly endpoint - the SDK Connection Endpoint. The SDK Connection Endpoint provides readonly access to a subset of your feature flag data, just enough for the [GrowthBook SDKs](/lib) to assign values to users. They are meant to be public and do not require authentication to view. In **GrowthBook Cloud**, the SDK Connection Endpoints are served from our global CDN: `https://cdn.growthbook.io/api/features/...`. If you are self-hosting, you can use [your own CDN](/self-host/cdn) or run the [GrowthBook Proxy server](/self-host/proxy), which provides built-in caching and performance optimizations. SDK Connection Endpoints are scoped to a single Environment (e.g. `dev` or `production`) and can also be scoped to a single Project. Manage all of your SDK Connections on the **Features → SDKs** page. > [accordion] Typescript Type Definition > > ```ts theme={null} > interface SDKEndpointResponse { > status: 200; > features: { > [key: string]: FeatureDefinition > } > } > > interface FeatureDefinition { > defaultValue: any; > rules?: FeatureDefinitionRule[]; > } > > interface FeatureDefinitionRule { > force?: any; > weights?: number[]; > variations?: any[]; > hashAttribute?: string; > hashVersion?: number; > seed?: string; > namespace?: [string, number, number]; > key?: string; > coverage?: number; > condition?: any; > meta?: VariationMeta[]; > name?: string; > phase?: string; > } > > interface VariationMeta = { > passthrough?: boolean; > key?: string; > name?: string; > } > ``` > [accordion] Example JSON object > > ```json theme={null} > { > "status": 200, > "features": { > "feature-key": { > "defaultValue": true > }, > "another-feature": { > "defaultValue": "blue", > "rules": [ > { > "condition": { > "browser": "firefox" > }, > "force": "green" > } > ] > } > } > } > ``` ### Encryption If you've enabled encryption for your SDK endpoint, the response format changes: > [accordion] Typescript Type Definition > > ```ts theme={null} > interface SDKEncryptedEndpointResponse { > status: 200; > encryptedFeatures: string; > } > ``` > [accordion] Example JSON object > > ```json theme={null} > { > "status": 200, > "encryptedFeatures": "abcdef123456GHIJKL0987654321..." > } > ``` You will need to decrypt the features first before passing into the SDK. Our front-end SDKs (JavaScript and React) handle this for you automatically and we're in the process of adding built-in support to our other SDKs. # Choose Your Data Path Source: https://docs.growthbook.io/app/choose-data-path Decide between GrowthBook's Managed Warehouse, Event Forwarder, or connecting your own data warehouse. GrowthBook connects to a data warehouse to query your experiment data and calculate results. You have three options: * **Managed Warehouse** — we host a ClickHouse warehouse for you and provide built-in event tracking. * **Event Forwarder** — you keep your own warehouse, and GrowthBook streams feature and experiment exposure events (plus any custom events you send) into it for you. * **Bring Your Own Warehouse (BYOW)** — you connect your existing warehouse with your own event pipeline already feeding it. ## Quick decision **Do you already have a data warehouse?** * **No** → [Managed Warehouse](/app/managed-warehouse) gets you running quickly with zero infrastructure setup. * **Yes — and I already have feature/experiment events flowing into it** → [Bring Your Own Warehouse](/warehouses) lets you keep your existing pipelines. GrowthBook queries your data directly. * **Yes — but I don't want to build or maintain the event pipeline myself** → [Event Forwarder](/app/event-forwarder) writes feature exposure, experiment exposure, and custom events into dedicated tables in your warehouse for you. *(Free plans include 1 million events per month; Pro and Enterprise plans are billed per event. Available for BigQuery, Snowflake, and Databricks.)* **Not sure?** Read the comparison below. ## Comparison | | Managed Warehouse | Event Forwarder | Bring Your Own Warehouse | | - | - | - | - | | **Setup time** | Minutes. One click to provision. | Minutes once your warehouse is connected. | Varies. Depends on your warehouse and existing pipeline. | | **Infrastructure** | Fully managed by GrowthBook (ClickHouse). | You own the warehouse; GrowthBook manages the ingestion pipeline and writes destination tables. | You manage both the warehouse and the event pipeline. | | **Event tracking** | Built-in via SDKs or Ingestion API. | Built-in via SDKs or Ingestion API; events land in your warehouse. | Use your existing event pipeline. | | **Data ownership** | Stored in GrowthBook-hosted ClickHouse. | Stays in your warehouse. | Stays in your warehouse. | | **Supported warehouses** | ClickHouse (managed). | BigQuery, Snowflake, Databricks | BigQuery, Snowflake, Redshift, Databricks, Postgres, ClickHouse, Athena, and more. | | **Plan** | Available on all plans. | Pro or Enterprise. | Available on all plans. | | **Cost** | Included events by plan + overage pricing. | Plan price + usage-based ingestion fees + your warehouse storage costs. | Free in GrowthBook; you pay your warehouse costs. | | **Best for** | Teams getting started, no existing warehouse. | Teams that own a warehouse but don't want to build/maintain an event pipeline. | Teams with existing event pipelines and analytics infrastructure. | ## Choose Managed Warehouse if... * You don't have a data warehouse set up yet. * You want the fastest path from signup to your first experiment result. * You're evaluating GrowthBook and want to see the full experience without infrastructure setup. * Your team is small and you'd rather not manage warehouse infrastructure. * You want built-in event tracking with automatic enrichment (geo, device, UTM data). [Get started with Managed Warehouse →](/app/managed-warehouse) ## Choose Event Forwarder if... * You already own a data warehouse (BigQuery, Snowflake, or Databricks) but don't have a production-grade event pipeline writing exposures into it. * You want feature usage and experiment exposure events captured automatically from GrowthBook SDKs, with the option to send any other custom events via SDK helpers or our Ingestion API. * You'd like to keep your data in your own warehouse so it can be joined with the rest of your organization's data. * You're on (or willing to upgrade to) the Pro or Enterprise plan and are comfortable with usage-based ingestion pricing. How it works at a glance: GrowthBook provisions destination tables in your warehouse (`feature_usage`, `experiment_views`, and a general `events` table). SDK plugins automatically forward feature/experiment events; everything else goes through our Ingestion API. Events are enriched and streamed into your warehouse in near real-time. [Learn more about Event Forwarder →](/app/event-forwarder) ## Choose Bring Your Own Warehouse if... * You already have event data flowing into BigQuery, Snowflake, Databricks, Redshift, or another warehouse. * You need to join experiment data with other business data (revenue, CRM, product usage). * Your organization has data governance requirements about where data is stored. * You want full control over your data pipeline and retention policies. * You're self-hosting GrowthBook. [Connect your warehouse →](/warehouses) ## Can I switch later? Yes. You can add additional data sources (Managed Warehouse, Event Forwarder, or BYOW) at any time, and you can use more than one simultaneously. For example, you might start on Managed Warehouse for quick prototyping, then move production experiments to your own warehouse via Event Forwarder once you're ready. ## What's next * [Managed Warehouse setup guide](/app/managed-warehouse) — provision quickly and send your first event * [Event Forwarder](/app/event-forwarder) — connect your warehouse and let GrowthBook handle event delivery * [Connecting to your data warehouse](/warehouses) — step-by-step guides for each supported warehouse * [Metrics and Fact Tables](/app/metrics) — define what you want to measure in your experiments # Data Pipeline Mode Source: https://docs.growthbook.io/app/data-pipeline Enable Pipeline Mode so GrowthBook writes intermediate tables in your warehouse and reuses them to cut experiment query cost. Enterprise **Pipeline Mode** is available on Enterprise plans. Availability differs by warehouse and pipeline strategy. Pipeline mode will provide substantial savings for experimenters with lots of data and/or long-running experiments. With **Pipeline Mode**, GrowthBook writes intermediate tables back to your warehouse and re-uses those across metric analyses in an experiment. Pipeline mode supports two strategies: * **Incremental refresh (Recommended)** — On each update, only scans new rows in your experiment assignment and metric sources, and persists reusable intermediate tables across updates to minimize re-scans and speed up analyses. * **Ephemeral (Legacy)** — Per-analysis, short-lived tables are created with limited retention for reuse within an analysis window. ## Incremental refresh (Recommended, in Beta) Our main pipeline mode reduces cost and speeds up experiment updates by scanning only new data since the last run, while keeping a small set of reusable, de-duplicated tables in your warehouse. Incremental refresh is only available for BigQuery, Snowflake, and Presto/Trino. Please reach out to support if your warehouse is not supported. ### How it works On each experiment update, GrowthBook only scans new data in your warehouse to keep the amount of data consumed to a minimum. Then, GrowthBook materializes several tables in your warehouse to ensure old data does not need to be scanned again, and to reduce the computation needed in future analyses. Specifically, for the experiment assignment query and fact tables, GrowthBook uses the max timestamp seen in the previous update and only scans for data since that last update, inserting that data into materialized intermediate tables to be used for analysis. ![A flow diagram illustrating an incremental data refresh pipeline for experimentation, organized into three interconnected tracks: Units, Metrics, and CUPED. New data inputs on the left—such as recent experiment exposures and metric facts—merge with existing historical data to update intermediate materialized tables in the center. Data flows sequentially from the Units table to the Metrics tables and then to the CUPED tables. Finally, the processed data from all three stages feeds into a single "Statistics" output on the right to compute analysis results.](https://mintcdn.com/growthbook-ea15456d/hKkmiLOwhH6E2xcD/static/images/incremental-refresh-diagram.jpg?fit=max\&auto=format\&n=hKkmiLOwhH6E2xcD\&q=85\&s=bcb461c367b4728add2047912bfba38a) ### Main benefits * Dramatic reduction in the number of rows scanned per update, as GrowthBook only has to scan your experiment assignment queries and fact tables for data in partitions that are on or after the last timestamp seen in the previous update. * Dramatic reduction in the cost and waiting time when running on-demand dimension analyses, as GrowthBook can use the existing materialized tables, `Experiment Units Table` and one or more `Units-Date Metrics Table` to get dimension breakdowns rather than re-scanning data in your warehouse. ### Requirements * A dedicated schema/dataset with write access for GrowthBook. * Ability to retain intermediate tables beyond 24 hours (often days) for reuse. * Appropriate permissions to create/read/update (and when supported, merge/partition) tables. * Experiment assignment queries and fact tables partitioned on the `timestamp` column. If you have a different partitioning strategy, please reach out to support. To see specific instructions for your warehouse, please see the [Configuration](#configuration) section below. ### Controls and observability * Default "Update" performs an incremental refresh * "Full refresh" is available when you need to re-state results (e.g., backfills, config changes) ### Limitations and notes * While in Beta, the following analysis tools aren't supported: activation metrics and dimension breakdowns in Experiment Dashboards. * Event-level quantile metrics are supported for BigQuery and Snowflake only and rely on approximate inference techniques. Small effects may be undetectable and low sample sizes may not produce reliable results. * Changes to analysis settings sometimes require a full data refresh to take effect. In the future, we will make improvements to ensure that only the data that need be restated is restated. * Any changes to fact table sources require using a full refresh to take effect, but we are working on improvements to ensure that only the data that need be restated is restated. * Exact table schemas and partitioning/retention strategies vary by warehouse. ### Enable Incremental refresh 1. In your Data Source, open "Data Pipeline Settings" 2. Enable Pipeline Mode and select the strategy: "Incremental refresh (Recommended)" 3. Set a destination schema/dataset for intermediate tables and choose retention settings 4. Save You can test incremental refresh by enabling it only for a single experiment in the Data Pipeline Settings UI. We do not apply it to old experiments in the case that they have incompatible settings. To confirm pipeline mode is being used, you can look at the queries run for your experiment and validate that they are creating intermediate tables. ### Additional technical details On each incremental update, GrowthBook: 1. Scans the experiment assignment source for any new rows that have a timestamp greater than the max timestamp seen on the previous run and updates a de-duplicated units table (one row per unit with timestamps and experiment dimensions). 2. Scans each metric's fact source for any new rows that have a timestamp greater than the max timestamp seen on the previous run and updates pre-aggregated metric tables (one row per unit-date-refresh with timestamps and metric dimensions). 3. Optionally loads pre-experiment windows for CUPED into a separate table for only newly seen units. 4. Joins the above to produce summary statistics and per-dimension breakdowns. Therefore, GrowthBook will materialize the following tables per experiment: * The units table (`Experiment Units Table` - named `gb_units_` in your warehouse) * one row per unit in the experiment with the unit id, variation id, first exposure timestamp, and experiment dimensions * this table is joined to new exposures, de-duped, and then re-created on each update * One or more metrics tables depending on the number of metric sources and the number of metrics (`Unit-Date Metrics Table` - named `gb_metrics___` in your warehouse) * one row per unit-date-refresh, as we take new metric values and aggregate them by unit and date on each refresh and insert them into the metrics table * we insert new rows into this table on each update * An additional table per above metric source for CUPED (`CUPED Metrics Table` - named `gb_cuped____covariate` in your warehouse) * one row per unit, as we scan the metric source for the pre-experiment window, join it to only new units found in the units update, and then insert the aggregated values per unit into the CUPED table * we insert new rows into this table on each update On each update, we look for new rows in your sources. We define "new" rows as those where the row has a timestamp greater than the max timestamp seen on the previous run. This means that if any data is re-stated in your warehouse, or new rows land that are linked to old timestamps, GrowthBook may miss this data unless you do a full refresh. In other words, as data lands, it must have a later timestamp than the data as of the last update or it will not be included in the analysis. This also means that if a Fact Table or Experiment Assignment Table definition includes references to multiple source tables, these tables should be updated at the same time to ensure GrowthBook can capture all data. GrowthBook is building functionality to support additional use cases, improve control over intermediate table retention, and support additional data sources. Please reach out to support if you have any concerns or questions. ## Configuration > [!NOTE] > > **Pipeline mode requires write permissions** > > While Pipeline mode requires you to give write permissions to your Data Source, GrowthBook only uses it for the Pipeline Mode. All other user-provided SQL statements are validated to be read-only before being executed. ### BigQuery 1. (strongly recommended, but optional) Create a dedicated dataset to which GrowthBook will write tables. This will keep your data warehouse clean and ensure that we are only writing to a dedicated space. 2. Grant permissions to create tables to the role connecting GrowthBook to your warehouse. You can do this by granting your GrowthBook Service Account the `BigQuery Data Editor` role on the new dataset. You can also give only BigQuery table reading and writing permissions on that dataset if you want to be more restrictive. 3. Navigate to your BigQuery Data Source in GrowthBook and scroll down to "Data Pipeline Settings" 4. Click "Edit" and enable pipeline mode, set the destination dataset to your new dedicated GrowthBook dataset from step 1. ### Snowflake 1. (strongly recommended, but optional) Create a dedicated schema to which GrowthBook will write tables. This will keep your data warehouse clean and ensure that we are only writing to a dedicated space. 2. Grant permissions to create tables to the role connecting GrowthBook to your warehouse. The Snowflake role attached to GrowthBook will need `CREATE TABLE`, `SELECT - FUTURE TABLE`, and `USAGE` on the schema created in step 1. For Incremental Refresh, it may also need `OWNERSHIP - FUTURE TABLE` and `MODIFY` permissions on that dedicated schema. 3. Navigate to your Snowflake Data Source in GrowthBook and scroll down to "Data Pipeline Settings" 4. Click "Edit" and enable pipeline mode, set the destination schema to your new dedicated GrowthBook schema from step 1. ### Databricks (Ephemeral only) Databricks works slightly differently. Instead of creating a temporary table, we create a regular table for the deduped units assignment and then `DROP` that table when analysis is completed. > [!NOTE] > > **Using pipeline mode in Databricks requires DROP permissions** > > Using pipeline mode in Databricks requires either granting DROP permissions to the Databricks account that GrowthBook uses, or leaving many tables in your schema you have to manually delete later! For this reason we strongly recommend a standalone schema for GrowthBook to use to write tables to. 1. (strongly recommended, but optional) Create a dedicated schema to which GrowthBook will write temporary tables. This will keep your data warehouse clean and ensure that we are only writing to and dropping from a dedicated space. 2. Grant permissions to your user account or service principal that already has read permission in your warehouse. That user/service principle will need to be able to `USE SCHEMA`, `CREATE TABLE`, `DROP TABLE`, and to `SELECT` and `EXECUTE` in the schema. 3. Navigate to your Databricks Data Source in GrowthBook and scroll down to "Data Pipeline Settings" 4. Click "Edit" and enable pipeline mode, set the destination schema to your new dedicated GrowthBook schema from step 1, and whether you want the table to be deleted (we recommend you leave this setting on as we will not re-use these tables at a later date). If this setting is off, you'll need to manually delete the tables that GrowthBook creates. # Data Source Configuration Source: https://docs.growthbook.io/app/datasources Connect a warehouse, pick an event tracker schema, and define assignment queries, identifier types, and join tables. ## Overview Data Sources are how GrowthBook connects to your data warehouse so that it can pull those aggregated statistics in order to compute metrics and experiment results. Each Data Source defines how to connect to your data, what version of SQL to use when querying your data, and can provide templates for what the SQL to connect to your Data Source should look like depending upon which event tracker software you use. GrowthBook works with your existing SQL data, no matter where it is located and no matter what shape or format it is in, whether you have a strongly normalized schema, a single “events” table with JSON fields, or something in between. ## Supported Event Schemas When adding a new Data Source in Growthbook from /datasources page we first guide you to select what event tracker software you use, or you can choose custom if you don't use any of the popular third party event trackers that we support. Telling us which event tracker you use, gives us an idea on the likely shape of your data. This will help us generate the correct sql to extract out the aggregated statistics from your site with as little modification on your end as possible. Here is Growthbook's current list of event trackers that we support with links for more details on how to set them up with GrowthBook: * [Amplitude](/event-trackers/amplitude) * [Arize Phoenix (Postgres only, beta)](/event-trackers/phoenix) * [CleverTap](/event-trackers/clevertap) * [Firebase](/event-trackers/firebase) * [Freshpaint](/event-trackers/freshpaint) * [Fullstory](/event-trackers/fullstory) * [Google Analytics 4 (BigQuery only)](/guide/GA4-google-analytics) * [Heap Analytics](/event-trackers/heap) * [Jitsu](/event-trackers/jitsu) * [Keen IO](/event-trackers/keenio) * [Langfuse (ClickHouse only, beta)](/event-trackers/langfuse) * [Matomo](/guide/matomo) * [Mixpanel](/guide/mixpanel) * [MParticle](/event-trackers/mparticle) * [RudderStack](/guide/rudderstack) * [Segment](/event-trackers/segment) * [Snowplow](/event-trackers/snowplow) If you do not use any of those you choose [Custom Data Source](/event-trackers/custom) and define some of the sql yourself. ## Configuration Settings Once you have chosen your event tracker and data source type and successfully connected, you will be given an opportunity to modify your configuration settings. For many applications Growthbook will have chosen the correct configuration settings straight out of the box based upon which event tracker you choose. In some instances, you may need to tweak them slightly, or in the case of using a custom datasource, define them more explicitly. ### Identifier Types These are all of the types of identifiers you use to split traffic in an experiment and track metric conversions. Common examples are `user_id`, `anonymous_id`, `device_id`, and `ip_address`. There are some cases where a single database column isn't enough to uniquely identify a subject. For example, you might need the combination of `company` and `user_id`. In this case, we recommend creating a synthetic identifier by concatenating all of the fields together. For example, you can create a `company_user` identifier and then in your SQL, select it as follows: `CONCAT(company, user_id) as company_user`. ### Experiment Assignment Queries An experiment assignment query returns which users were part of which experiment, what variation they saw, and when they saw it. Each assignment query declares one or more identifier types (defined above), and each experiment picks the one it's analyzed on. You can also have multiple assignment queries if you store that data in different tables, for example, one from your email system and one from your back-end. > [!NOTE] > > **Queries used to generate experiment results** > > Assignment queries are one-half of the queries that are used to generate experiment results, the other being metric queries. Assignment queries can be edited from the `Metrics and Data` → `Data Sources` page. The end result of the query should return data like this: | user\_id | timestamp | experiment\_id | variation\_id | | - | - | - | - | | 123 | 2021-08-23-10:53:04 | my-button-test | 0 | | 456 | 2021-08-23 10:53:06 | my-button-test | 1 | The above assumes the identifier type you are using is `user_id`. If you are using a different identifier, you would use a different column name. Here's an example query you might use: ```sql theme={null} SELECT user_id, received_at as timestamp, experiment_id, variation_id FROM events WHERE event_type = 'viewed experiment' ``` Make sure to return the exact column names that GrowthBook is expecting. If your table’s columns use a different name, add an alias in the SELECT list (e.g. `SELECT original_column as new_column`). #### Duplicate Rows If a user sees an experiment multiple times, you should return multiple rows in your assignment query, one for each time the user was exposed to the experiment. This helps us detect when users were exposed to more than one variation, and eventually may be useful in helping build interesting time series. #### Experiment Dimensions In addition to the standard 4 columns above, you can also select additional dimension columns. For example, `browser` or `referrer`. These extra columns can be used to drill down into experiment results. #### Identifier Join Tables If you have multiple identifier types and want to be able to auto-merge them together during analysis, you also need to define identifier join tables. For example, if your experiment is assigned based on `device_id`, but the conversion metric only has a `user_id` column. These queries are very simple and just need to return columns for each of the identifier types being joined. For example: ```sql theme={null} SELECT user_id, device_id FROM logins ``` #### SQL Templates GrowthBook uses [Handlebars](https://handlebarsjs.com/guide/#language-features) to compile our SQL queries and we expose several variables and helper functions to make your SQL dynamic and reusable. This applies to experiment assignment queries, fact tables, and metrics. Read more about [SQL Templates](/app/sql-templates) and the different variables that are available. ### Jupyter Notebook Query Runner This setting is only required if you want to export experiment results as a Jupyter Notebook. There is no one standard way to store credentials or run SQL queries from Jupyter notebooks, so GrowthBook lets you define your own Python function. It needs to be called `runQuery`, accept a single string argument named `sql`, and return a pandas data frame. Here's an example for a Postgres (or Redshift) data source: ```python theme={null} import os import psycopg2 import pandas as pd from sqlalchemy import create_engine, text # Use environment variables or similar for passwords! password = os.getenv('POSTGRES_PW') connStr = f'postgresql+psycopg2://user:{password}@localhost' dbConnection = create_engine(connStr).connect(); def runQuery(sql): return pd.read_sql(text(sql), dbConnection) ``` **Note:** This python source is stored as plain text in the database. Do not hard-code passwords or sensitive info. Use environment variables (shown above) or another credential store instead. ## Schema Browser When you connect a supported data source to GrowthBook, we automatically generate metadata that is used by our Schema Browser. The Schema Browser is a user-friendly interface that makes writing queries easier as you can easily explore information about the datasource such as databases, schemas, tables, columns, and data types. ![GrowthBook Schema Browser](https://mintcdn.com/growthbook-ea15456d/1rsmujQCDzXz2Vho/static/images/growthbook-schema-browser.png?fit=max\&auto=format\&n=1rsmujQCDzXz2Vho\&q=85\&s=f8e2dbc90db6dabde2a92fd35fa0d989) Below are the data sources that currently support the Schema Browser: * AWS Athena - *Requires a Default Catalog* * BigQuery - *Requires a Project Name and Default Dataset* * ClickHouse * Databricks - *Currently only supported on version 10.2 and above with a Unity Catalog* * MsSQL/SQL Server * MySQL/MariaDB * Postgres * PrestoDB (and Trino) - *Requires a Default Catalog* * Redshift * Snowflake > [!NOTE] > > **Generating the schema manually** > > If you added a supported data source prior to GrowthBook v2.0, you can generate the schema manually by clicking "Data Sources" on the left-nav, selecting the data source, and then clicking the "View Schema Browser" button and following the on-screen prompt. # Dimensions Source: https://docs.growthbook.io/app/dimensions Break experiment results down by browser, country, or other dimensions from assignment queries or a dimension table. Dimensions let you drill down into experiment results. For example, if you define a `browser` dimension, you can see how Safari users behaved vs Chrome. Be careful, the more dimensions and metrics you look at for an experiment, the more likely you are to see false positives - something that looks significant when it really isn't. For example, if you break out results by country, it's pretty likely that at least one of the 100+ countries in your dataset will be significantly different just by random chance. It's best to treat dimensions as an exploratory tool and not something to directly draw conclusions from. The two best use cases are identifying bugs (the `browser` example) and getting ideas for dedicated follow-up experiments. Dimensions are supported for both Mixpanel and SQL data sources. ## Custom dimensions In addition to the dimensions you define, the dimension selector on experiment results has a **Custom** group with two dimensions you build on the fly. Neither requires any setup on your data source. ### First exposed after... Pick a datetime (in UTC) within the experiment phase. Units are split into two slices based on whether they were *first* exposed to the experiment before or after that time. This is useful for checking whether a fix, release, or traffic change partway through the experiment affected results. ### Combination of dimensions Pick two different dimensions (Experiment Dimensions or User Dimensions) and GrowthBook breaks results down by their intersection, with one slice per pair of values such as `country: US & plan: premium`. As with any dimension, only the top 20 slices are kept and the rest are grouped into `(other)`. Because two dimensions multiply the number of slices, keep the number of values in each small to avoid the false positive issues discussed above. Both are also available in the REST API when creating a snapshot: use `cutoff:` (e.g. `cutoff:2026-01-15T00:12:00.000Z`) or `combo:::` (e.g. `combo:exp:country::dim_abc123`) as the `dimension`. ## SQL There are two types of dimensions for SQL data sources: Experiment Dimensions and User Dimensions. Experiment dimensions are more reliable for producing unbiased experiment analyses because we can always know the dimension that is associated with the first experiment exposure for a unit. Using dimension data that could be affected by the experiment (e.g. that is collected *after* the first experiment exposure) could bias dimension drill-downs. Therefore, use user dimensions with caution, and we suggest using immutable dimensions as your user dimensions (e.g. the first client the user ever used, or the first country the user ever logged in from). For all experiment analyses, we only ever choose one dimension per user, to avoid the aforementioned issues with potential bias. For Experiment Dimensions, we select the dimension associated with the user's first exposure event for the experiment. For User Dimensions, we strongly suggest only having one dimension value per user, as we cannot discern what dimension a user had before the experiment. Instead, we simply choose the `MAX(value)` for the user dimension. ### Experiment Dimensions These are attributes that are specific to the point-in-time that a user was put into an experiment. For example, `browser` or `referrer`. Instead of a standalone SQL query, experiment dimensions are simply extra columns you return from the Experiment assignment query defined for your data source. As an example, if you set the following as your experiment assignment query: ```sql theme={null} SELECT user_id, received_at as timestamp, experiment_id, variation_id, browser FROM experiment_viewed ``` The first 4 columns are standard, but `browser` is a custom one that can be used as an Experiment Dimension. #### Configuring Experiment Dimensions To power the health tab and pre-computed dimension results, you need to configure your Experiment Dimensions. You can do this by clicking the "Configure Dimensions" button in the kebab menu next to the exposure query where the Experiment Dimensions are defined: ![Configure Dimensions Option](https://mintcdn.com/growthbook-ea15456d/giSZyh24PfnTkKz4/static/images/configure-dimensions.png?fit=max\&auto=format\&n=giSZyh24PfnTkKz4\&q=85\&s=17951d5c518d1826c2ab5d2f01d7ddcd) In the modal that pops up, you can run a query to find the top 20 dimension slices for your Experiment Dimensions and save them for use on the health tab. If you want to have more control over the pre-defined slices, the best way to do so is to modify the Exposure Query SQL to include a `CASE` statement that defines the dimension slices. For example, if you wanted to have a `browser` dimension with slices for `Chrome`, `Safari`, and `Other`, you could do something like this: ```sql theme={null} SELECT user_id, received_at as timestamp, experiment_id, variation_id, CASE WHEN browser LIKE '%Chrome%' THEN 'Chrome' WHEN browser LIKE '%Safari%' THEN 'Safari' ELSE 'Other' END as browser FROM experiment_viewed ``` ### User Dimensions These are attributes of your users that are relatively stable over time, and ideally, do not change over the course of an experiment. For example, `cohort`, `initial age group`, or `first client used`. A user dimension is defined by a simple SQL query that returns two columns: an identifier and `value`. The name of the identifier column depends on which identifier type the dimension is using. Remember, when using these for analysis, we will pick the `MAX(value)` for each user, and therefore it is best to only have one value per user. Here's an example SQL using an immutable attribute (the user's first country): ```sql theme={null} -- Assumes identifier type is "user_id" SELECT user_id, first_country as value FROM users ``` It's best to keep the number of unique values for a dimension small if possible to avoid the False Positive issues discussed above. We automatically cap the number at 20, but you can do it yourself if you want more control. Here's an example using the "first\_country" dimension: ```sql theme={null} SELECT user_id, ( CASE WHEN first_country = 'us' THEN 'US' WHEN first_country = 'uk' THEN 'UK' ELSE 'Other' END ) as value FROM users ``` If you need to use a dimension that can change over time, you should use the `{{ startDate }}` template variable to pin it to the value the user had before the experiment started: ```sql theme={null} SELECT user_id, plan_type as value FROM subscriptions WHERE timestamp < '{{ startDate }}' ``` > [!TIP] > > **SQL template variables** > > You can use `{{ startDate }}`, `{{ endDate }}`, and `{{ experimentId }}` in dimension queries to scope your data to the experiment's time window. See the [SQL Templates](/app/sql-templates) page for more details. ## Mixpanel For mixpanel, there is just a single type of dimension that is based on event properties (at this time Mixpanel user properties are not supported). For simple dimensions, you can just put the event property name directly. For example: `$browser`. We also support complex JavaScript expressions. For example: ``` event.properties.$browser.match(/chrome/i) ? "Chrome" : "Other" ``` For more complex expressions, you can wrap your code in an anonymous function: ``` (() => { // ...some complex logic return dimensionValue })() ``` You can also reference the experiment start/end date in your JavaScript expression. For example, if you add a super event called `userRegistrationDate` that stores a unix timestamp, you could make a `New vs Existing` dimension like this: ``` event.properties.userRegistrationDate >= {{ startDateUnix }} ? "new" : "existing" ``` The variables you can reference are: * **startDate** - `YYYY-MM-DD HH:mm:ss` of the earliest data that needs to be included * **startYear** - Just the `YYYY` of the startDate * **startMonth** - Just the `MM` of the startDate * **startDay** - Just the `DD` of the startDate * **startDateUnix** - Unix timestamp of the startDate (seconds since Jan 1, 1970) * **endDate** - `YYYY-MM-DD HH:mm:ss` of the latest data that needs to be included * **endYear** - Just the `YYYY` of the endDate * **endMonth** - Just the `MM` of the endDate * **endDay** - Just the `DD` of the endDate * **endDateUnix** - Unix timestamp of the endDate (seconds since Jan 1, 1970) * **experimentId** - Either a specific experiment id OR `%` if you should include all experiments # Event Forwarder Source: https://docs.growthbook.io/app/event-forwarder Forward GrowthBook Cloud tracking events to your warehouse so you can analyze experiments without instrumenting a tracker first. GrowthBook offers a fully managed event ingestion pipeline. Use our SDKs to track events in your app and we will enrich and forward them to your data warehouse in near real-time. ## Benefits * **Fully Managed**: No need to worry about infrastructure management, scaling, or maintenance. * **Seamless Integration**: Built-in tracking in our SDKs. * **Near Real-Time**: Events are available in your warehouse typically within a few seconds. * **Broad Support**: Works with BigQuery, Snowflake, and Databricks today, with more warehouses coming soon. ## How it Works 1. Provide GrowthBook with your data warehouse connection details (see [Connection Permissions](#connection-permissions) for required permissions) 2. We will create destination tables for `events`, `experiment_viewed`, and `feature_usage` 3. You send analytics events to our scalable Ingestion API 4. We enrich the events and stream them into your warehouse within seconds 5. We will build a Fact Table, customize the Data Source's experiment assignment queries, and feature usage queries, so you can quickly start analyzing your experiment data and defining metrics. ## Connection Permissions When setting up the Event Forwarder, GrowthBook uses your data warehouse connection to pre-create destination tables and validate write access. ### BigQuery The service account configured in your GrowthBook BigQuery datasource must have the **BigQuery Data Editor** role on the destination dataset. This grants the necessary permissions to: * Check whether destination tables exist * Create the destination tables (`events`, `experiment_viewed`, `feature_usage`) * Write data via the BigQuery Storage Write API You can grant this at the dataset level in the BigQuery console or via the `roles/bigquery.dataEditor` IAM role binding. ### Snowflake The Snowflake connection **must use key-pair authentication** — password authentication is not supported for the Event Forwarder. GrowthBook creates the three destination tables itself during setup, then streams events into them through [Snowpipe Streaming](https://docs.snowflake.com/en/user-guide/snowpipe-streaming/data-load-snowpipe-streaming-overview) as the connection's user. Snowpipe Streaming runs under the user's **default role**, so the role you enter in the Event Forwarder must be that user's default role (`ALTER USER SET DEFAULT_ROLE = `). Setup creates the tables and checks them as that same role, so it needs the following privileges: `CREATE TABLE` and `CREATE SCHEMA` are required because we use Snowpipe Streaming with schema evolution, which may create or alter tables and schemas as your event schema changes. Key-pair authentication must be configured on the Snowflake datasource connection itself — it is not set in the Event Forwarder UI. Update your datasource's connection settings to use key-pair auth before enabling the Event Forwarder. ### Databricks #### Before you start: is Zerobus available for your workspace? The Databricks Event Forwarder writes through [Zerobus Ingest](https://www.databricks.com/product/data-engineering/lakeflow-connect/zerobus-ingest), which is not available everywhere yet. Check three things: * **Region.** Open the workspace switcher in the top navigation bar to see your workspace region, then confirm it has a check in the Zerobus Ingest column of Databricks' availability table for your cloud: [AWS](https://docs.databricks.com/aws/en/resources/feature-region-support#ingestion), [Azure](https://learn.microsoft.com/en-us/azure/databricks/resources/feature-region-support#ingestion), [Google Cloud](https://docs.databricks.com/gcp/en/resources/feature-region-support#ingestion). * **Unity Catalog.** Zerobus writes only to Unity Catalog managed tables. The catalog on your GrowthBook connection must be a Unity Catalog catalog, not `hive_metastore`. * **Databricks OAuth (machine-to-machine)** on the connection, as described below. Setup creates the tables through your SQL warehouse and does not test Zerobus itself. If your region is unsupported, setup succeeds and the forwarder shows an error after the first event arrives. The Databricks connection **must use Databricks OAuth (machine-to-machine) authentication** with a Databricks-issued client ID and secret. Personal access tokens keep working for queries, but the Event Forwarder writes through [Zerobus Ingest](https://www.databricks.com/product/data-engineering/lakeflow-connect/zerobus-ingest), which only accepts Databricks OAuth tokens. Switch the datasource connection to Databricks OAuth before enabling the Event Forwarder. Grant the service principal the following on the destination catalog and schema. GrowthBook creates the three Delta tables itself, so it also gets `SELECT` and `MODIFY` on them as their owner. ```sql theme={null} GRANT USE CATALOG ON CATALOG TO ``; GRANT USE SCHEMA, CREATE TABLE ON SCHEMA . TO ``; ``` In the Event Forwarder UI you provide: * **Catalog**, pre-filled from your connection when it names one. * **Schema**, an existing schema the service principal can create tables in. We suggest a dedicated one such as `growthbook`. * **Table prefix** (optional, default `gb`). GrowthBook creates `_events`, `_experiment_viewed`, and `_feature_usage` in that schema, clustered on `received_at`, with `attributes` and `properties` stored as `VARIANT`. * **Zerobus endpoint** for your workspace. See [Finding your Zerobus endpoint](#finding-your-zerobus-endpoint) below. #### Finding your Zerobus endpoint The endpoint is not shown in the Databricks UI; you build it from two values: | Cloud | Endpoint | | - | - | | AWS | `https://.zerobus..cloud.databricks.com` | | Azure | `https://.zerobus..azuredatabricks.net` | | Google Cloud | `https://.zerobus..gcp.databricks.com` | * **Workspace ID** is the long number in your workspace URL. On AWS and Google Cloud it follows `?o=` (for example `https://dbc-a1b2c3d4-e5f6.cloud.databricks.com/?o=1234567890123456`). On Azure it follows `adb-` in the hostname (`https://adb-1234567890123456.7.azuredatabricks.net`). GrowthBook pre-fills it where the connection host carries it. * **Region** is the cloud region your workspace runs in, for example `us-east-1`, `eastus`, or `us-central1`. It is shown in the Databricks account console under **Workspaces**. Zerobus Ingest is a serverless Databricks feature billed to **your** Databricks account per GB ingested. No SQL warehouse or cluster runs on your side to receive events; the SQL warehouse configured on the datasource is only used to create the tables and to run your experiment queries. ## Network Access For [Event Forwarder](/app/event-forwarder), you must always allow incoming connections from GrowthBook Cloud's primary ip address (`52.70.79.40/32`). If you are using our `eu-west-1` ingestor region, you also must allow traffic from `54.74.233.97/32`. ## Usage Limits Event Forwarder is included on every GrowthBook Cloud plan. * **Free plans** can forward up to 1,000,000 events per calendar month (UTC). You'll see a notification in GrowthBook when you reach 80% of the limit. Once the limit is exceeded, events stop being forwarded for the remainder of the month and forwarding resumes automatically on the 1st of the next month. Events that arrive while forwarding is stopped are accepted but not delivered. * **Pro and Enterprise plans** have no cap. Forwarded events are billed per event; see your plan for rates. Usage is counted once per event. Month-to-date totals and daily history are on **Settings > Usage**, and the Event Forwarder card on your Data Source shows the current month's progress. ## Get Started Event Forwarder is available to every GrowthBook Cloud organization. Open **Settings > Data Sources**, add or edit a BigQuery, Snowflake or Databricks Data Source, and set up the Event Forwarder from its **Event Forwarder** card. Free plans are subject to the [usage limits](#usage-limits) above; one Event Forwarder is allowed per organization. If you have questions about higher volumes or Enterprise options, reach out to [sales@growthbook.io](mailto:sales@growthbook.io). ## Sending Events There are 2 ways to send events to GrowthBook: 1. With our [SDKs](#with-sdks) 2. With our [Ingestion API](#ingestion-api) ### With SDKs These SDKs have a built-in plugin that automatically sends feature usage and experiment view events to GrowthBook, with the SDK's attributes as context. Don't include sensitive information in your attributes (or anonymize it first). For other SDKs, use the Ingestion API below. > [tab] HTML Script Tag > > Add `data-tracking="growthbook"` to the script tag. It reports events that already fired; it does not enroll users in an experiment. > > ```html theme={null} > > ``` > > Push custom events to the `window.gbEvents` array: > > ```html theme={null} > > ``` > > | Attribute | Description | > | - | - | > | `data-event-ingestor-host` | Ingestor for your Data Region. | > | `data-event-transport` | `auto` (default), `beacon`, or `fetch`, like the JavaScript SDK's `transport` option. | > [tab] JavaScript / React > > Requires `@growthbook/growthbook` v1.4.0 or later. We recommend also adding `autoAttributesPlugin` to include common attributes (browser, `session_id`, UTM parameters, etc.) with every event. > > ```js theme={null} > import { GrowthBook } from "@growthbook/growthbook"; > import { > autoAttributesPlugin, > growthbookTrackingPlugin, > } from "@growthbook/growthbook/plugins"; > > const gb = new GrowthBook({ > clientKey: "YOUR_CLIENT_KEY", > plugins: [ > autoAttributesPlugin(), > growthbookTrackingPlugin({ > ingestorHost: "https://us-east-1.gb-ingest.com", > }), > ], > }); > ``` > > Log custom events with `logEvent`. In React, get the instance with `useGrowthBook()`. > > ```js theme={null} > // Without properties > gb.logEvent("Page View"); > > // With properties > gb.logEvent("Button Click", { button: "Sign Up" }); > ``` > > | Option | Description | > | - | - | > | `ingestorHost` | Ingestor for your Data Region. | > | `enableFeatureUsageEvents` | Set to `false` to stop sending feature usage events. Default `true`. v1.8.0+. | > | `transport` | `auto` (default), `beacon`, or `fetch`. See below. v1.7.0+. | > > Events are batched and sent with `fetch` using `keepalive`. When the page is hidden or unloading, the queue is flushed with `navigator.sendBeacon`. Both survive the page being torn down, so exposures aren't lost when a user navigates away immediately, which matters most for redirect experiments. Set `transport: "beacon"` to always prefer `sendBeacon`, or `"fetch"` to never use it. > [tab] Node.js > > Requires `@growthbook/growthbook` v1.7.0 or later. > > ```js theme={null} > import { GrowthBookClient } from "@growthbook/growthbook"; > import { growthbookTrackingPlugin } from "@growthbook/growthbook/plugins"; > > const gb = new GrowthBookClient({ > clientKey: "YOUR_CLIENT_KEY", > plugins: [ > growthbookTrackingPlugin({ > ingestorHost: "https://us-east-1.gb-ingest.com", > }), > ], > }); > ``` > > Log custom events with `logEvent`, passing the user context: > > ```js theme={null} > gb.logEvent("Sign Up", { accountPlan: "pro" }, userContext); > ``` > > User-scoped instances (for example `req.growthbook` from the [Express middleware](/lib/node#express-middleware)) don't need the user context: > > ```js theme={null} > req.growthbook.logEvent("Sign Up", { accountPlan: "pro" }); > ``` > > Set `enableFeatureUsageEvents: false` (v1.8.0+) to stop sending feature usage events. > [tab] Python > > Requires `growthbook` v2.2.0 or later, plus the `requests` package: > > ```bash theme={null} > pip install growthbook requests > ``` > > ```python theme={null} > from growthbook import GrowthBook, GrowthBookTrackingPlugin > > gb = GrowthBook( > client_key="YOUR_CLIENT_KEY", > attributes={"id": "user_123"}, > plugins=[ > GrowthBookTrackingPlugin( > ingestor_host="https://us-east-1.gb-ingest.com", > ) > ], > ) > gb.load_features() > ``` > > Log custom events with `log_event`: > > ```python theme={null} > gb.log_event("Button Click", {"button": "Sign Up"}) > ``` > > Call `gb.destroy()` when you're done with the instance to flush pending events. > > | Option | Description | > | - | - | > | `ingestor_host` | Ingestor for your Data Region. | > | `track_feature_evaluated` | Set to `False` to stop sending feature usage events. Default `True`. | > | `batch_size` | Events to queue before sending. Default `10`. | > | `batch_timeout` | Seconds to wait before sending a partial batch. Default `10.0`. | > > > [!NOTE] > > > > Use the `GrowthBook` class. The async `GrowthBookClient` does not send events to GrowthBook yet. > [tab] PHP > > Requires `growthbook/growthbook` v2.4.0 or later. > > ```php theme={null} > use Growthbook\Growthbook; > use Growthbook\GrowthBookTrackingPlugin; > use Growthbook\GrowthBookTrackingPluginConfig; > > $growthbook = Growthbook::create() > ->withAttributes(['id' => $userId]) > ->addPlugin(new GrowthBookTrackingPlugin( > new GrowthBookTrackingPluginConfig( > ingestorHost: "https://us-east-1.gb-ingest.com", > ) > )); > > $growthbook->initialize("YOUR_CLIENT_KEY"); > ``` > > Call `addPlugin()` after any `with*()` methods. Those return a copy of the instance, and a discarded copy closes the plugin. The plugin gets its client key from `initialize()`, and pending events are sent when the request ends. > > Custom events aren't supported yet. Send them with the [Ingestion API](/app/managed-warehouse#ingestion-api). > > | Option | Description | > | - | - | > | `ingestorHost` | Ingestor for your Data Region. | > | `batchSize` | Events to queue before sending mid-request. Default `100`. | > [tab] Go > > Requires `github.com/growthbook/growthbook-golang` v0.5.0 or later. > > ```go theme={null} > import ( > "context" > > gb "github.com/growthbook/growthbook-golang" > ) > > client, err := gb.NewClient( > context.Background(), > gb.WithClientKey("YOUR_CLIENT_KEY"), > gb.WithSseDataSource(), > gb.WithGrowthBookTracking(gb.TrackingPluginConfig{ > IngestorHost: "https://us-east-1.gb-ingest.com", > }), > ) > if err != nil { > // handle error > } > defer client.Close() // sends pending events > ``` > > Log custom events with `LogEvent`, on a client scoped to the current user. Don't call `Close()` on that client; it closes the shared plugin. > > ```go theme={null} > userClient, _ := client.WithAttributes(gb.Attributes{"id": "user-123"}) > > userClient.LogEvent(context.Background(), "Button Click", gb.EventProperties{"button": "Sign Up"}) > ``` > > | Option | Description | > | - | - | > | `IngestorHost` | Ingestor for your Data Region. | > | `BatchSize` | Events to queue before sending. Default `100`. | > | `BatchTimeout` | How long to wait before sending a partial batch. Default `10s`. | > [tab] Java > > Requires the GrowthBook Java SDK v0.11.1 or later. > > ```java theme={null} > import growthbook.sdk.java.multiusermode.GrowthBookClient; > import growthbook.sdk.java.multiusermode.configurations.Options; > import growthbook.sdk.java.multiusermode.configurations.UserContext; > import growthbook.sdk.java.plugin.tracking.GrowthBookTrackingPlugin; > import growthbook.sdk.java.plugin.tracking.TrackingPluginConfig; > import java.util.Collections; > > String clientKey = "YOUR_CLIENT_KEY"; > > GrowthBookTrackingPlugin trackingPlugin = GrowthBookTrackingPlugin.of( > TrackingPluginConfig.builder() > .clientKey(clientKey) > .ingestorHost("https://us-east-1.gb-ingest.com") > .build() > ); > > GrowthBookClient client = new GrowthBookClient( > Options.builder() > .apiHost("https://cdn.growthbook.io") > .clientKey(clientKey) > .plugins(Collections.singletonList(trackingPlugin)) > .build() > ); > > if (!client.initialize()) { > client.shutdown(); > throw new IllegalStateException("GrowthBook initialization failed"); > } > > UserContext user = UserContext.builder() > .attributesJson("{\"id\":\"user-123\"}") > .build(); > > boolean enabled = client.isOn("my-feature", user); > ``` > > The plugin automatically sends feature usage and experiment view events, with the user's attributes as context. The plugin doesn't inherit the client's SDK connection key, so pass it to both `TrackingPluginConfig` and `Options`. > > Keep one client for your application and create a separate tracking plugin for each client. At application shutdown, call `client.shutdown()` to flush pending events and release resources: > > ```java theme={null} > client.shutdown(); > ``` > > Custom events aren't supported yet. Send them with the [Ingestion API](/app/managed-warehouse#ingestion-api). > > | Option | Description | > | - | - | > | `ingestorHost` | Ingestor for your Data Region. | > | `batchSize` | Events to queue before sending. Default `100`. | > | `batchTimeout` | How long to wait before sending a partial batch. Default `Duration.ofSeconds(10)`. | > | `closeTimeout` | Time budget for flushing events during shutdown. Default `Duration.ofSeconds(5)`. | > [tab] Kotlin > > Requires the GrowthBook Kotlin SDK v7.8.0 or later, with `NetworkDispatcherKtor` v1.2.0+ or `NetworkDispatcherOkHttp` v1.1.0+. > > ```kotlin theme={null} > import com.sdk.growthbook.GBSDKBuilder > import com.sdk.growthbook.network.GBNetworkDispatcherKtor > import com.sdk.growthbook.plugin.TrackingPluginConfig > import com.sdk.growthbook.plugin.tracking.GrowthBookTrackingPlugin > > val networkDispatcher = GBNetworkDispatcherKtor() > > val growthBook = GBSDKBuilder( > apiKey = "YOUR_CLIENT_KEY", > apiHost = "https://cdn.growthbook.io/", > attributes = attributes, > networkDispatcher = networkDispatcher, > trackingCallback = { _, _ -> }, > ) > .setPlugins( > listOf( > GrowthBookTrackingPlugin( > TrackingPluginConfig( > clientKey = "YOUR_CLIENT_KEY", > networkDispatcher = networkDispatcher, > ingestorHost = "https://us-east-1.gb-ingest.com", > ) > ) > ) > ) > .initialize() > ``` > > The plugin doesn't inherit the builder's client key or network dispatcher, so pass both to `TrackingPluginConfig`. Without a network dispatcher, no events are sent. > > Custom events aren't supported yet. Send them with the [Ingestion API](/app/managed-warehouse#ingestion-api). > > | Option | Description | > | - | - | > | `ingestorHost` | Ingestor for your Data Region. | > | `batchSize` | Events to queue before sending. Default `100`. | > | `batchTimeout` | How long to wait before sending a partial batch. Default `10.seconds`. | > [tab] Swift > > Requires the GrowthBook Swift SDK v1.1.15 or later. > > ```swift theme={null} > import GrowthBook > > let gb: GrowthBookSDK = GrowthBookBuilder( > apiHost: "https://cdn.growthbook.io", > clientKey: "YOUR_CLIENT_KEY", > attributes: ["id": "user-123"], > trackingCallback: { _, _ in } > ) > .addPlugin(GrowthBookTrackingPlugin( > config: .init(ingestorHost: "https://us-east-1.gb-ingest.com") > )) > .initializer() > ``` > > Custom events aren't supported yet. Send them with the [Ingestion API](/app/managed-warehouse#ingestion-api). > > | Option | Description | > | - | - | > | `ingestorHost` | Ingestor for your Data Region. | > | `batchSize` | Events to queue before sending. Default `100`. | > | `batchTimeout` | Seconds to wait before sending a partial batch. Default `10`. | #### Sending events to the right region The tracking plugin sends events to `https://us-east-1.gb-ingest.com` by default. If you selected **eu-west-1** as your Event Forwarder's Data Region, set the ingestor host option shown for your SDK above to `https://eu-west-1.gb-ingest.com`. Otherwise events won't reach the proper ingestion server and your events will be dropped. You can find your Event Forwarder's configured region on the data source's Event Forwarder settings. ### Ingestion API You can also send events directly to our ingestion API. Pass an array of event objects, each with the following properties: * **event\_name**: The name of the event (e.g., "Purchase", "Button Click") * **properties**: Optional key-value pairs with properties of the event itself * **attributes**: Optional key-value pairs with attributes of the user or context at the time of the event * YOUR\_WAREHOUSE\_REGION: either us-east-1 or eu-west-1 depending on what you selected when creating your datasource * YOUR\_CLIENT\_KEY: The key from your sdk connection. Go to [https://app.growthbook.io/sdks/](https://app.growthbook.io/sdks/) click on your connection or make a new one. Copy the text from the "Client Key" ```bash theme={null} curl -X POST "https://YOUR_WAREHOUSE_REGION.gb-ingest.com/track?client_key=YOUR_CLIENT_KEY" \ -H "Content-Type: application/json" \ -d '[{ "event_name": "Purchase", "properties": { "amount": 100 }, "attributes": { "user_id": "12345" } }]' ``` Using the wrong host means events won't reach the proper ingestion server and your events will be dropped. #### Experiment View Events In order to use GrowthBook's experiment analysis features, you must send an event every time a user views an experiment. It must match the following format: * **event\_name**: Must be `"Experiment Viewed"` * **properties**: Must include the following key/value pairs: * `experimentId`: The ID of the experiment being viewed * `variationId`: The ID of the variation that was shown to the user In addition, you must include `attributes` with the user attributes that were used to evaluate the experiment, plus any attributes you want to use as dimensions for slicing and dicing. #### Feature Usage Events To take advantage of GrowthBook's feature usage analytics, you must send an event every time a feature is evaluated with a specific format. * **event\_name**: Must be `"Feature Evaluated"` * **properties**: Must include the following key/value pairs: * `feature`: The name of the feature being evaluated * `value`: The feature's value that was returned from the evaluation * `source`: (optional) The source of the feature value (e.g., "defaultValue", "experiment", "force") * `ruleId`: (optional) The ID of the specific rule that was used to evaluate the feature (or `$default` if the default value was used) * `variationId`: (optional) If the value came from an experiment, the ID of the variation that was returned #### Attributes Attributes are key-value pairs that provide context about the user or environment at the time of the event. They can be used to slice and dice your data in analysis. It's recommended to include the same attributes you use in your GrowthBook SDK. Some attributes are enriched in the ingestion API if provided: * `ip` - a geoip lookup is done and the following attributes are added. If an ip attribute is not provided, we will use the IP address of the request. * `geo_country` * `geo_city` * `geo_lat` * `geo_lon` * `ua` - the user agent is parsed and the following attributes are added. If a user agent attribute is not provided, we will use the user agent of the request. * `ua_browser` (e.g. Safari) * `ua_os` (e.g. macOS) * `ua_device_type` (e.g. mobile) * `url` - the URL is parsed and the following attributes are added: * `url_path` (e.g. /products/123) * `url_host` (e.g. [www.example.com](http://www.example.com)) * `url_query` (e.g. ?utm\_source=google) * `url_fragment` (e.g. #section1) It's also recommend to include attributes about which SDK is being used. This can help with debugging. * `sdk_language` (e.g. python) * `sdk_version` (e.g. 1.2.3) #### Limits When calling the ingestion API directly, please be aware of the following default limits: * Maximum of 100 events per request * Maximum of 1 request per second These limits are to protect accidental misuse of the API. Our Ingestion API can handle much higher volumes than this. Reach out to your account manager if you need to increase your limits. # Experiment Configuration Source: https://docs.growthbook.io/app/experiment-configuration Create an experiment in GrowthBook, set variations and targeting, then configure analysis and review results. # Experiment Configuration The Experiments section in GrowthBook is where you create experiments, configure their analysis, and review results. Before [analyzing results](/app/experiment-results), you need to run the experiment. GrowthBook supports several ways to do this: * [Feature Flags](/features/feature-flag-experiments) (most common) * Running an inline experiment directly with our [SDKs](/lib) * [URL Redirects](/app/url-redirects) * Our [AI Visual Editor](/app/visual) * Your own custom variation assignment / bucketing system ## Creating an Experiment GrowthBook uses a slim creation flow: you start by creating a minimal experiment, then configure everything else afterward on the experiment's **Overview** page. To create a new experiment, go to the [**Experiments** ](https://app.growthbook.io/experiments) page and click **Add → Create New Experiment**. The creation modal only asks for the essentials: * **Name** * **Project** (if you use projects) * **Template** (optional, but recommended — see below) * **Holdout** (optional) * **Hypothesis** * **Assignment Attribute** — the attribute hashed together with the tracking key to bucket users into variations * **Custom Fields** — optional [custom fields](/using/growthbook-best-practices#custom-fields) added by your organization to experiments GrowthBook creates the experiment as a **draft** with two default variations and, when possible, auto-selects your data source and experiment assignment table. You are then taken to the experiment's Overview tab, where you finish setup (see [Configuring an Experiment](#configuring-an-experiment)). ### Start From a Template Templates are the fastest way to create a consistent experiment. Selecting a **Template** in the creation modal pre-fills settings like the data source, goal/secondary/guardrail metrics, targeting, and traffic, so there is less to configure afterward. You can also require that all new experiments use a template under **Settings → General → Experiment Settings**. See the [Experiment Templates](/running-experiments/experiment-templates) guide for details. ### Finish Setup With the Pre-Launch Checklist After creation, the **To Do** tab in the draft experiment's details panel holds a pre-launch checklist that guides you through the remaining steps, such as: * Selecting a data source and experiment assignment table * Adding at least one goal metric * Adding a linked [Feature Flag](/features/feature-flag-experiments), [AI Visual Editor](/app/visual) change, or [URL Redirect](/app/url-redirects) The pre-launch checklist can be configured by Enterprise customers, [read more here](/app/pre-launch-checklist). Analyzing an experiment you ran elsewhere? See [Importing existing experiments](/app/importing-experiments). ## Configuring an Experiment Most configuration happens after creation, on the experiment's Overview tab. There are several different ways to configure your experiment analysis. ### Experiment Metadata On the experiment page in the "Overview" tab near the top of the page, you can see the experiment name, tags description, hypothesis, and variation metadata. You can edit these fields as you see fit to help describe and categorize your experiment. ### Experiment Targeting and Traffic You can also configure targeting and traffic to your experiment's linked feature flags or AI Visual Editor changes. These settings do not have any effect on an experiment that is performing analysis only (with the exception of "experiment key"). On the "Overview" tab, you will see a section called "Targeting and Traffic" which allows you to modify these settings, such as: **Experiment Key** (tracking key) - This is the key that will be used when filtering your experiment assignment source to query experiment exposure data. **Hashing attribute** - This is the attribute that will be used to hash the user id to determine which variation they will be bucketed into. **Fallback attribute** (Sticky Bucketing enabled only) - The Fallback attribute will be used when the hash attribute is missing or empty. For example falling back to an anonymous cookie identifier instead of a logged-in user id. Whichever attribute is first used to bucket the user into a variation will "stick". For example, if the user is logged out when they first view an experiment, it would use the fallback device id. If the user later logs in, it will continue using the bucket from their device id, even though they now have a logged-in id as well. **Targeting** - Create matching conditions using attributes and saved groups or target by namespaces. **Traffic** - Choose the percentage of traffic (coverage) and set the relative weights of each variation. ### Analysis Settings Here is where much of your experiment analysis is configured. On the experiment's Overview tab, this section is accessed via the **Edit Settings** button — the label shown on the button that opens the Analysis Settings form. Many of the fields here have some text explaining how they affect your analysis. Here are a few of them in more detail: > [!NOTE] > > **Permissions required** > > The **Edit Settings** button is only visible to users with permission to edit the experiment's analysis settings (for example, the `Admin`, `Experimenter`, or `Project Admin` roles, or a custom role with equivalent permissions). If you don't have one of these roles, the button will not appear, and you'll need to ask an organization or project admin to make changes on your behalf. See [User Permissions](/account/user-permissions) for details on roles. **Experiment Key** **Activation Metric** - A binomial metric that will filter the users in your analysis to only those who have converted on this binomial metric. This should only be used if activation is expected to be independent of experiment assignment. If an experiment affects the activation metric directly, then there is the potential for bias in your analysis. **Metric Conversion Windows** - Some of your metrics may have "conversion windows" defined for them. For those metrics, we build a window for each user based on when they were first exposed to the experiment. If a user's first exposure to an experiment was recent (for a running experiment) or near the end of a stopped experiment, they may not have had the full window to convert before the analysis window closes. You may exclude these users with "In-Progress Conversions" if you want your experiment averages to only include those who have had the full window to convert. **Conversion Override** Previously called "Attribution Model" in the UI, this setting lets you use an experiment level override to disable all conversion windows and instead use the exposure period for a user (from their first exposure until the end of the experiment) as the metric window. You can read more about Metric Windows on the [Metric documentation page](/app/metrics). * **Respect Conversion Windows** - This setting ensures that all conversion windows on your metrics are respected. * **Ignore Conversion Windows** - This setting overrides all metrics to be as if they had no conversion windows. Lookback windows will not be overridden by this setting. For those familiar with the "Experiment Duration" attribution model, choosing "Ignore Conversion Windows" will work the same way, and you should know that you can now specify the metric window behavior on a metric-by-metric basis (see the [Metric documentation page](/app/metrics)). ### Experiment Metrics You can add metrics as goal metrics, guardrail metrics, or both. You also can add "Metric Overrides" which provide experiment-specific controls for metrics, allowing you to override the metric's defaults for, for example, metric windows and priors. ### Experiment Phases Experiment Phases are used primarily to separate stages of an experiment that are to be analyzed separately (e.g. you needed to restart an experiment and change variation weights). For analyzing by specific date ranges, use [Custom Reports](/app/experiment-results#creating-a-custom-report) instead. Be aware that if you move the start date of your analysis to later than the actual start date of the experiment or a new phase (or users were not re-randomized across phases), then your analysis could suffer from [carryover bias](/kb/experiments/carryover-bias). It is best to always look at the full history of an experiment if possible. ### Making Changes While Running Read our dedicated guide on [making changes to running experiments](/app/making-experiment-changes). ## Temporary Rollouts When an experiment finishes and you enable a Temporary Rollout to ship the winning variation, the variation continues to be delivered through the experiment's Feature Flags, URL Redirects and AI Visual Editor changes. Temporary Rollouts are explicitly named **temporary** because long-term you should re-implement the winning variation in your site's code. When you no longer need the rollout, stop it from the experiment to improve your site performance. The rollout keeps the experiment's targeting and goes to every user who meets it, whatever the experiment's rollout percentage was. Without a Temporary Rollout, the experiment stops serving as soon as you stop it. Its rules stay on its Feature Flags but no longer apply. For users in the experiment, each flag checks the rules below the experiment's rule, in order, and serves its default value if none match. Its URL Redirects and AI Visual Editor changes stop too, so users see the original pages. # Experiment Dashboards Source: https://docs.growthbook.io/app/experiment-dashboards Create a tailored view of your experiment. Highlight key insights, add context, and share a clear story with your team. Enterprise **Dashboards** is available on Enterprise plans. Use experiment dashboards to create tailored views of your experiment. Highlight key insights, add context, and share a clear story with your team. ![Experiment Dashboard](https://mintcdn.com/growthbook-ea15456d/giSZyh24PfnTkKz4/static/images/experiment-dashboard-editor.png?fit=max\&auto=format\&n=giSZyh24PfnTkKz4\&q=85\&s=a91af75edded15152bf5bea172b1b1d1) ## Creating Dashboards To create a new dashboard, go to the experiment you want to create a dashboard for and click the **Dashboards** tab. If there are no dashboards yet, click the **Create Dashboard** button. If there are existing dashboards, the first one will be open automatically when you go to the tab. To create a new dashboard, open the select box in the top left, which lets you switch between dashboards. There will be an option there to **Create New Dashboard**. Give the dashboard a name and select a few options: * **Auto-update dashboard data** - If enabled, the dashboard will automatically refresh its data whenever the experiment results are updated. If disabled, the dashboard will only refresh when you manually click the update button. * **View access** - Control whether your organization members can view your new dashboard. * **Edit access** - Share the ability to edit dashboard contents with your organization. Even when editing is shared, only the owner and organization admins can change access permissions. After creating the dashboard, you will be able to add blocks to it. ## Dashboard Blocks An experiment dashboard consists of a set of blocks. There are several types of blocks you can add: * **Markdown**: Add custom formatted text, images, and links to provide context and insights. * **Experiment Metadata**: Display the description, hypothesis, and variation names/screenshots for the experiment. * **Experiment Traffic**: Show the count of units in each experiment variation. Choose between a simple table or a timeseries chart. * **Metric Results**: Include the main experiment results table, with the ability to customize the metrics and columns that are included. * **Dimension Results**: Show a breakdown of a specific metric by a chosen dimension (e.g., country, device type). You can select which metric and dimension to display. * **Time Series**: Display a timeseries chart for specific metrics within an experiment, allowing you to visualize trends over time. * **SQL Explorer**: Show a visualization from a custom SQL query. This is useful for displaying data that isn't directly available in the standard experiment results. ## Use a Dashboard as the Default Experiment View You can set an experiment dashboard as the default view to present a more curated, stakeholder-friendly summary of your experiment results. This is particularly useful for presenting findings from stopped experiments in a way that's easier for non-technical stakeholders to understand. > [!NOTE] > > Setting a dashboard as the default view for an experiment will require experiment edit permission. **To set a dashboard as the default view:** 1. Navigate to the experiment's **Dashboards** tab 2. Select the dashboard you want to use 3. Click the **More** menu (⋯) and select **Edit Dashboard Settings** - **View access** must be set to **Organization members** 4. Click the **More** menu (⋯) and select **Set as Default View** **To revert to the standard view:** Follow the same steps but select **Remove as Default View** from the More menu. When a dashboard is set as the default view, you can still access the standard experiment view by clicking **View Regular Experiment View** above the dashboard. # Experiment Decision Framework Source: https://docs.growthbook.io/app/experiment-decisions Use the Experiment Decision Framework to set success criteria, target MDEs, and decide whether to keep running or ship an experiment. Pro **Decision Framework** is available on Pro and Enterprise plans. The Experiment Decision Framework (EDF) is a set of tools and customizable settings to help streamline and improve experiment decision making. The EDF can help answer questions like, "Should I keep running my experiment?" and "Do my results meet my success criteria?" The EDF has two key components: 1. The Decision Criteria 2. The Target Minimum Detectable Effects (MDEs) for your goal metrics ### Decision Criteria The **Decision Criteria** are the ways that your results in an experiment lead to a decision. Do all goal metrics need to be statistically significant and positive to ship, or are you happy to ship so long as none are statistically significant and negative? These can be customized or you can use our two out-of-the-box options: (1) Clear Signals, which requires no guardrail failures and all goal metric successes, or (2) Do No Harm, which only requires that no metrics are statistically significant in the harmful direction. ![Example Decision Criteria](https://mintcdn.com/growthbook-ea15456d/giSZyh24PfnTkKz4/static/images/experiment-decision-framework/decision-criteria.png?fit=max\&auto=format\&n=giSZyh24PfnTkKz4\&q=85\&s=99e0df4dc64cf50cde896d3e93f97366) ### Target Minimum Detectable Effects The **Target Minimum Detectable Effects** (MDEs) are the smallest effects you want to be able to reliably detect before making a decision. For example, suppose 10% is your target MDE value (10% is the GrowthBook default). If your confidence intervals range from 10 to 20%, then you have reached your desired precision. Once this desired precision is reached, your experiment has collected enough data for the decision criteria to be applied and a ship, rollback, or review decision is given for your experiment. Note: sometimes a decision is given before the target MDE is reached if either (1) results are significant at a very strict threshold or (2) if you have sequential testing enabled. Once an experiment has reached the needed precision, we make a recommendation based on the Decision Criteria for your experiment. ![Example Rendered Decision](https://mintcdn.com/growthbook-ea15456d/giSZyh24PfnTkKz4/static/images/experiment-decision-framework/experiment-decision.png?fit=max\&auto=format\&n=giSZyh24PfnTkKz4\&q=85\&s=fa4955d2588a716e41832aeca052af55) ## Setting up the Experiment Decision Framework You can set up the Experiment Decision Framework under **Settings > General > Experiment Settings**. ![Experiment Decision Framework Organization Settings](https://mintcdn.com/growthbook-ea15456d/giSZyh24PfnTkKz4/static/images/experiment-decision-framework/organization-settings.png?fit=max\&auto=format\&n=giSZyh24PfnTkKz4\&q=85\&s=500566e384e1826484a7fea19157586f) 1. Under `Settings > General > Experiment Settings`, set the `Minimum experiment runtime`, which stops the decision framework from displaying days remaining or experiment decisions while early data collection is ongoing. The default value is 3 days, but larger values such as 7 may be reasonable, especially if there are strong day of the week effects in your data. 2. Set the `Target Minimum Detectable Effects` (target MDEs) for your key goal metrics. You can set the default for all metrics on the `Settings > General > Metric & Data` tab. The GrowthBook default is 10%, with [selection guidance](#selecting-target-minimum-detectable-effects) below. 3. Create the `Decision Criteria` that you want your experiments to follow under `Settings > General > Experiment Settings`. 4. Review your Experiment creation process! The EDF relies heavily upon using your Goal Metrics to determine how long your experiment needs to run as well as what decision to make. If you have 3 or more goal metrics, or even 2 competing goal metrics, it can take a lot longer for your experiment to finish and reach a clear shipping recommendation. Consider setting only one goal metric, and use secondary metrics to deep dive your results and build more understanding. ### Customizing the decision framework per experiment The above process lets you select defaults for your Organization, but the decision framework can also be customized per experiment. On the Experiment Overview Tab, under Analysis Settings, you can customize the target MDEs for the goal metrics in your experiment or change the decision criteria to apply to the experiment. ![Experiment Decision Framework Settings](https://mintcdn.com/growthbook-ea15456d/giSZyh24PfnTkKz4/static/images/experiment-decision-framework/experiment-decision-framework-settings.png?fit=max\&auto=format\&n=giSZyh24PfnTkKz4\&q=85\&s=bc7ec03941dfedeab681df54a3653217) ### Selecting Target Minimum Detectable Effects The target MDE is the smallest effect you want to be able to reliably detect before making a decision. For example, suppose 10% is your target MDE value (10% is the GrowthBook default). If your confidence intervals range from 0 to 20%, then a 10% lift would be statistically significant, your target MDE has been reached, and your experiment is ready for decision. Please note that the time at which you reach your target MDE is unaffected by the lift estimate in your experiment. In the above example, only the inputted target MDE and the width of your confidence interval are used to determine if you have reached your target MDE. If your estimated lift in your experiment was 5%, but your confidence interval ranged from -5% to 15%, your results would not be statistically significant even though you reached your target MDE. That means you're comfortable making a ship or roll back decision with this amount of power, even if your results are not statistically significant. If your estimated lift was 20% with the same interval width, then your results would be statistically significant, and you should ship. The target MDE helps ensure that the experiments you run are well-powered, not that results from an individual experiment will be statistically significant. Your target MDE should balance the lift at which it is profitable to ship against the time needed to run the experiment. Small target MDEs will let you detect tinier effects before being ready to make a decision, but they will require more data. Large target MDEs will shorten experiment runtimes, but if your true effects are smaller than your target MDEs, you will often face ambiguity at the end of your experiment. ## Experiment Statuses Experiments have a variety of statuses represented as badges on the Experiment Detail page and in the Experiment List based on whether they are in Draft, Running, or are Stopped. For Stopped experiments, your stated reason for calling the experiment is included in the status. For running experiments, there are a variety of statuses, each depending on the current state of your experiment and your settings. Many statuses have an associated tooltip that explains them in more detail. Note: only `unhealthy` and `no data` statuses appear before your experiment has been running for your organization's `minimum experiment duration`, which defaults to 3 days. | Status | Status conditions | What to do next | | - | - | - | | Unhealthy | This status appears when your experiment results has imbalanced traffic (SRM), multiple exposures, or is low-powered. | For SRM or multiple exposures, [see the troubleshooting doc](/kb/experiments/troubleshooting-experiments). For low-powered experiments, see the FAQ below. | | No data | This status appears when your experiment results have refreshed, but there are no users showing up in your experiment. | You have no traffic or are missing some datasource and metric configurations. There are three common causes if you have no traffic: (1) you just started your experiment and you just need to wait for your experiment exposure data to land in your data warehouse before it will show up in GrowthBook; (2) you started your experiment but there are no linked feature flags, AI Visual Editor changes, or URL redirects attached (see the Experiment Overview > Implementation section) that are actually exposing users to your experiment and getting traffic; (3) you have some issue configuring your Experiment Assignment Query or something else in your GrowthBook set-up that is likely affecting multiple experiments ([more information here](/kb/experiments/troubleshooting-experiments)). | | \~X days left | This status appears when your experiment has not yet reached the [target power](#what-is-target-power) and it estimates how much longer you need to run the experiment to reach that target. | Continue to collect data until your target power is reached, or the experiment has been running longer than is feasible for your organization's goals. If the estimated duration is quite high, it is likely you have: (1) too many goal metrics, (2) your target minimum detectable effect (MDE) for your goal metrics is too small for your traffic, or (3) you are not getting very much new traffic in the experiment. We recommend selecting only 1 or 2 goal metrics and consider establishing more realistic target MDEs for your key goal metrics. | | Ship now | This status appears when your decision criteria to Ship Now have been met for at least one variation AND either (1) Sequential Testing is enabled in our Frequentist engine OR (2) the goal metrics have reached their target MDEs (3) OR the effects are statistically significant at a strict threshold (Frequentist engine: p-value \< 0.001; Bayesian engine: CTW > 99.9%). | Ship the variation that fits the above criteria! | | Roll back now | This status appears when your decision criteria to Rollback Now have been met for all test variations AND either (1) Sequential Testing is enabled in our Frequentist engine OR (2) the goal metrics have reached their target MDEs (3) OR the effects are statistically significant at a strict threshold (Frequentist engine: p-value \< 0.001; Bayesian engine: CTW > 99.9%) | Stop the experiment and keep the status quo! | | Ready for review | This status appears when your decision criteria to Review have been met for any variation AND either (1) Sequential Testing is enabled in our Frequentist engine OR (2) the goal metrics have reached their target MDEs (3) OR the effects are statistically significant at a strict threshold (Frequentist engine: p-value \< 0.001; Bayesian engine: CTW > 99.9%) cases. | Consider the trade-offs between shipping, rolling back and iterating, or continuing to run the experiment in these ambiguous cases. | ## FAQ ### What is target power? Your experiment reaches its "target power" when it has enough data to reliably detect the target minimum detectable effect for all of your goal metrics. More data will help get more precise results, but based on the level of precision you want for your goal metrics, you have enough data to make a decision. ### What should I do if my experiment has many days remaining or low power? In these cases, the experiment traffic is not high enough to reliably detect the target MDE you have set for all of your goal metrics. This can often happen when: 1. Your target MDE is too low for your traffic and metric - if you run low traffic experiments, or if you have a conversion metric that is very rare (e.g. less than 1%), then it can take a lot of data to precisely estimate effects. It is possible your target MDE for your metric is too low. Consider evaluating whether you are comfortable making decisions with more uncertainty, and consider increasing your target MDE in the settings for that metric. 2. You have too many goal metrics - ideally you only have one or two goal metrics that decide whether or not you will launch an experiment variation. Picking a small number of goal metrics makes it easier to get enough power to make a decision. It forces you to clearly state up front your goals for your experiment, which makes decision making more straightforward. Finally, it can protect you from making up goals to fit your results, rather than making sure your results fit your organization's goals. 3. You have too many variations - having more variations splits your traffic and lowers the power you have to detect effects. # Experiment Results Source: https://docs.growthbook.io/app/experiment-results Analyze A/B test results in GrowthBook: statistical analysis, health checks, custom reports, and how to interpret the data. Once your experiment is up and running, you will be able to track how it is performing in the Experiment Results tab. This can be found on any Experiment page under the Results tab at the top. There, you can update your analysis, configure how you want your analysis to run, and see the impact of your experiment on your metrics. ## Experiment Results Table The heart of the experiment results page is the table of results. What you will see will depend a little bit on whether you are using our Bayesian or our Frequentist engine, as well as whether your experiment has 2 variations or if it has 3+, but in either case the first two data columns will look the same, and each row will represent one comparison between baseline and variation for one metric. ![Results Table](https://mintcdn.com/growthbook-ea15456d/J3C3juKhu0f7KEr_/static/images/results-table.png?fit=max\&auto=format\&n=J3C3juKhu0f7KEr_\&q=85\&s=64d384bab6842334f372056af3c602b2) In both engines, the first two data columns are: **Baseline** - the average value of the metric in the baseline variation; either a percentage of users in that variation (for proportion, retention, and funnel metrics) or an average value for mean or ratio metrics. **Variation** the average value of the metric in the comparison variation. For both columns, the raw data in grey underneath shows the numerator and denominator totals. ### Bayesian Engine **Chance to Win** tells you the probability that the variation is better. Anything above 95% (a customizable threshold set at the organization level) is highlighted green indicating a very clear winner. Anything below 5% is highlighted red, indicating a very clear loser. Anything in between is grayed out indicating it's inconclusive. If that's the case, there's either no measurable difference or you haven't gathered enough data yet. The graph and the **% Change** column show you how much better/worse the variation is compared to the baseline. It is a probability density graph and the thicker the area, the more likely the true percent change will be there. As you collect more data, the tails of the graphs will shorten, indicating more certainty around the estimates. ### Frequentist Engine If you select the "Frequentist" engine, when you navigate to the results tab to view and update the results, you will see the following results table: ![Results Table (Frequentist)](https://mintcdn.com/growthbook-ea15456d/J3C3juKhu0f7KEr_/static/images/results-table-frequentist.png?fit=max\&auto=format\&n=J3C3juKhu0f7KEr_\&q=85\&s=671ba8f7d079e132114208e907716a2d) The **P-value** column is the probability that the experiment effect for a variation would have been observed if the true effect was zero. When the p-value is less than 0.05 (a customizable threshold set at the organization level) and the experiment effect is in the preferred direction, we highlight the cell green, indicating it is a clear winner. When the p-value is less than 0.05 and the experiment effect is *opposite* the preferred direction, we highlight the cell red, indicating the variant is a clear loser on this metric. The graph now represents a 95% confidence interval (or 100\*(1 - $\alpha$)% confidence interval if you have a custom significance threshold other than 0.05). The *% Change* column is unaffected, although we now also represent the width of the confidence interval in grey. ## Metric Drilldown Clicking any metric or slice row in the results table opens the **Metric Drilldown** modal, which provides a focused view of that metric, its slices, and more debugging information. The modal includes three tabs: * **Overview** - Metric details and [time series](/app/experiment-time-series) for the metric, a link to the metric page, a results table for that metric with analysis controls (baseline, variation filter, difference type), and a metric card showing key information. * **Slices** - All slices for that metric displayed in a searchable, sortable table. Clicking a slice row in the main results table opens the modal with the Slices tab focused and that slice pre-filtered for quick exploration. This is also where time series for metric slices are displayed. * **Debug** - Debug information for power users to show the impact of: CUPED, Post-Stratification, Capping, and informative priors (Bayesian engine only). For [Funnel Metrics](/app/metrics/funnel), a **Funnel** tab replaces the Slices tab. See [Funnel Metric Results](#funnel-metric-results). ![Metric Drilldown modal - Overview tab](https://mintcdn.com/growthbook-ea15456d/giSZyh24PfnTkKz4/static/images/experiment-results/metric-drilldown-overview.png?fit=max\&auto=format\&n=giSZyh24PfnTkKz4\&q=85\&s=8b1d0ceeb69b44b1392b418b95c87999) ![Metric Drilldown modal - Debug tab](https://mintcdn.com/growthbook-ea15456d/giSZyh24PfnTkKz4/static/images/experiment-results/metric-drilldown-debug.png?fit=max\&auto=format\&n=giSZyh24PfnTkKz4\&q=85\&s=22a1051db4fe05dea8e7c9124462b964) For more information on how metric slices are defined, see [Metric Slices](/app/metrics#metric-slices), an Enterprise-only feature. ## Funnel Metric Results [Funnel Metrics](/app/metrics/funnel) measure the share of exposed units who complete an ordered series of steps. In the results table they appear as a single row marked with a funnel icon that expands into one row per step. ### Step rows Click the caret next to a funnel metric to **Expand funnel steps**. Each step gets its own row, labeled with the step name and `Step N` below it, plus `(optional)` for steps you marked optional. Every step row is an ordinary proportion, so it reports a baseline rate, a variation rate, a % change, and significance in the same columns as your other metrics. The parent row measures the share of units who reach the last step, which is the end-to-end completion rate. The denominator is the same for the parent row and for every step row: all exposed units in that variation. Step 3's rate is the share of exposed units who reached step 3, not the share of step 2 completers who continued. For step-to-step conversion, use the funnel chart below. Step rows always stay in funnel order, first step to last, no matter which column you sort by. Sorting reorders the parent metrics and keeps each funnel's steps grouped beneath it. Funnel rows expand the same way in the metric and time series blocks on [Experiment Dashboards](/app/experiment-dashboards). ### Funnel chart Clicking a funnel metric opens the [Metric Drilldown](#metric-drilldown) with a **Funnel** tab in place of the Slices tab. It charts every variation's funnel side by side, one color per variation, with a faded bar stacked above each step showing the drop-off from the previous step. The chart is normalized so that each variation's first step is 100%, which lets you compare shapes across variations of different sizes. Hovering a step shows each variation's unit count, its share of the first step, and its share of the previous step. > [!NOTE] > > Average time to convert between steps is not yet available for funnel metrics in experiments. The [Funnel explorer](/app/product-analytics/explorer#funnels) reports it for ad-hoc funnels. ### Dimensions and date results Funnel metrics support dimensions. When you break results down by [date](#date), each step gets its own chart under the funnel's heading, titled `Step N: `, with `(optional)` appended for optional steps. ### Downloading results CSV exports include one row per step alongside the funnel itself, with step rows named `Funnel name: Step name`. The [bulk experiment results API](/app/experiment-bulk-results-api) reports the same, using per-step metric ids of the form `?step=`. ### Funnel limitations * **CUPED and regression adjustment** are always off for funnel metrics and their steps. Support is coming soon! * **Metric slices** are not supported. The Slices tab is replaced by the Funnel tab. * **Activation metrics** cannot be funnel metrics. * **Incremental Refresh** does not support funnel metrics while the incremental pipeline is in beta. Support is coming soon! ## Guardrails Guardrail metrics are treated much like regular metrics, but they are placed in a separate part of the results view and are not part of any p-value corrections in the frequentist engine (in other words, even with p-value corrections applied, these results will be more sensitive to negative or positive trends). They also can be used differently from regular metrics in the [Experiment Decision Framework](/app/experiment-decisions). ## Results Table Settings There are several settings at the top of the results table that allow you to control your results. ### Variations This option allows you to filter which variations are shown in the results table, in the case that you have 3+ variations in your experiment. ### Baseline Variation This option allows you to change which variation is the baseline variation. This is particularly useful in the case when you have one control and two treatment variations. In this case, our result defaults to showing you the statistics comparing each treatment variation versus the baseline variation, but you may want to additionally analyze how the treatment variations compare to one another. In that case, you can switch the baseline to be one of the treatment variations to directly compare treatment 1 to treatment 2. ### Difference Types A "difference type" is the way we measure the difference in variations. There are three difference types that you can select from" * `Relative` - The default, this is the relative change or "uplift" of your variation when compared to the baseline. Specifically, the `Relative` change is $100\% * \frac{\mu_t - \mu_c}{\mu_c}$ where $mu_t$ and $\mu_c$ are the averages in the treatment and baseline variations respectively. Effects here tell you that the average user value in the variation was X% greater than the average user value in treatment. For example, if your metric is Revenue and your baseline average is 10.00 and your variation average is 10.31, then your `Relative` change is 3.1%. * `Absolute` - This is simply the difference in average values across your variations --- $\mu_t - \mu_c$. This can help you understand the raw difference in average values, e.g. the treatment leads to an increase in revenue of \$0.31 per user in the above example. * `Scaled Impact` - This helps you understand the daily total (as opposed to average) effect that your experiment would have had if 100% of users that would have been exposed to your treatment variation had gotten that treatment variation. It is computed as $(\mu_t - \mu_c) * (N_t / p_t) * (1 / d)$, where $N_t$ is the number of users that are in your treatment variation, $p_t$ is the percent of all traffic that is in that variation, and $d$ is the number of days in the current phase used for the results. So if your experiment ran on 10% of traffic for 20 days, with 5% going to the treatment variation, and there were 5,000 users in your treatment variation, the scaled impact would be $0.31 * 5,000 / 0.05 / 20 = 1,550$ dollars per day. This implies that this experiment would have lead to a $1,550$ increase in total revenue per day if every user that could have been exposed the variation had been exposed to the variation. These difference types can have slightly different statistics because for Relative effects we need to account for the uncertainty in estimating $\mu_c$ (which forces us to use a delta method derived variance to properly handle). For more details, see the [Statistical Details](/statistics/details). Furthermore, with CUPED enabled, you may find that the changes are not exactly the same as the difference in raw variation averages, due to CUPED adjusting those averages under the hood to reduce variance. ### Dimensions Dimensions allow you to slice and dice your data, but require additional queries to compute. #### User or Experiment If you have defined dimensions for your data source, you can use the **Unit Dimension** dropdown to drill down into your results. For SQL integrations (e.g. non-MixPanel) GrowthBook enforces one dimension per user to prevent statistical bias and to simplify analyses. For more on how GrowthBook picks a dimension when more than one are present for a user, see the [Dimensions documentation](/app/dimensions). This is very useful for debugging (e.g. if Safari is down, but the other browsers are fine, you may have an implementation bug) or for better understanding your experiment effects. Be careful. The more metrics and dimensions you look at, the more likely you are to see a false positive. If you find something that looks surprising, it's often worth a dedicated follow-up experiment to verify that it's real. #### Date The date dimension shows a time series of the count of users *first* exposed to an experiment, as well as effects when comparing users *first* bucketed on each day. Take the following results, for example. ![Experiment Date Results](https://mintcdn.com/growthbook-ea15456d/giSZyh24PfnTkKz4/static/images/experiment-date-results.png?fit=max\&auto=format\&n=giSZyh24PfnTkKz4\&q=85\&s=f0252e363cd21b3aa7edd0e75684485a) In the first graph, we see the number of users who were first exposed to the experiment on that day. In the second graph, we see the uplift for the two variations relative to the control *for all users first bucketed on that day*. That means that the values on October 7 show that users first exposed to the experiment on October 7 had X% uplift relative to control, when pooling all of the data in their relevant metric window. It does not show the difference in conversions across variations on October 7 for all previously bucketed users. #### Custom The **Custom** group in the dimension selector lets you build a dimension without adding anything to your data source: * **First exposed after...** splits units into two slices based on whether they were first exposed before or after a datetime you pick within the experiment phase. * **Combination of dimensions...** breaks results down by the intersection of two Experiment or Unit Dimensions. See [Custom dimensions](/app/dimensions#custom-dimensions) for details. ## Experiment Health By default, GrowthBook runs several health checks on your experiment to help you diagnose if there are any issues with the configuration. Health checks appear in two places in the app: 1. On the results tab -- we insert Experiment Balance and Multiple Exposures warnings for all experiments that might be untrustworthy right into your results tab 2. On the health tab -- if you have health traffic queries enabled, we can run additional queries to further dig into any experiment health issues ### Experiment Balance (SRM) Every experiment automatically checks for a Sample Ratio Mismatch (SRM) and will warn you if found. This happens when you expect a certain traffic split (e.g. 50/50) but you see something significantly different (e.g. 46/54). We only show this warning if the p-value is less than `0.001` (customizable in your Organization Settings), which means it's extremely unlikely to occur by chance. ![SRM Warning](https://mintcdn.com/growthbook-ea15456d/J3C3juKhu0f7KEr_/static/images/srm.png?fit=max\&auto=format\&n=J3C3juKhu0f7KEr_\&q=85\&s=8c3d480eeaa67d1afca44f389c5195ca) Like the warning says, you shouldn't trust the results since they are likely misleading. Instead, find and fix the source of the bug and restart the experiment. You can find more information about potential sources of the problems in our [troubleshooting guide](/kb/experiments/troubleshooting-experiments). Under the hood, we are conducting a standard chi-squared test for Sample Ratio Mismatch, which compares the distribution of observed units to the expected units and computes a p-value for the probability of observing this traffic split if the traffic split were truly unbiased. ### Pre-Exposure Bias Check This alert indicates that there is a large difference between the pre-exposure mean of the baseline variation and the pre-exposure mean of a test variation. This means that the baseline and test groups were substantially different with respect to a goal or guardrail metric before the experiment started. While some degree of imbalance is expected, this alert is raised if the imbalance is statistically significant. This matters because large pre-exposure mean imbalances can bias results, leading to incorrect shipping decisions. If SRM or multiple exposures fails, then these should be investigated first. If the pre-exposure mean imbalance is not caused by SRM or multiple exposures, then potential causes include: 1. outliers in the pre-exposure metrics: if a few extreme values are causing the imbalance, then consider [capping](/app/metrics#capped-value) your metric. 2. incorrect timestamps for aggregated data: if you are using daily [pre-aggregated data](/app/metrics/examples#pre-aggregated-tables), please ensure your daily metric timestamps are at the end of the day, rather than the start. This ensures that metric data from the first day of exposure always comes after exposure timestamps. 3. exposure latency: if there is a lag between when the exposure event happens and when it is recorded in the events table, then post-exposure metrics could be counted as pre-exposure. If your lag is large and cannot be reduced, then consider [metric delays](/app/metrics#metric-delay), which shift the pre-exposure window to exclude the lag period. 4. mismatch between randomization unit and analysis unit causing unreliable inference: if you are randomizing on session\_id but analyzing by user\_id, then attribution of the metric outcomes to multiple sessions for the same user can imbalance results. We use the frequentist engine with sequential testing disabled to calculate the p-values for the pre-exposure mean imbalance, with a significance threshold of 0.001. We calculate the p-values for the pre-exposure mean imbalance using a two-sample t-test on the absolute difference in means. We adjust for testing multiple metrics using a Bonferroni correction including all goal and guardrail metric-variation pairs as standalone tests. We exclude secondary metrics from causing the overall health status to fail as well as from contributing to the Bonferroni-corrected threshold. This means if you have 3 arms (2 variation test arms), 2 goal metrics, 35 secondary metrics, and 2 guardrail metrics, we would correct for a total of (2 goal × 2 arms + 2 guardrail × 2 arms) = 8 total tests. Note: we exclude metric slices and quantile metrics from the pre-exposure bias check. ### Multiple Exposures This alert indicates that there a substantial number of users (or other unit) in your experiment that have been exposed to multiple variations. At its core, this means that your Experiment Assignment Query is returning data that has rows that look like the following: | user\_id | timestamp | experiment\_id | variation\_id | | - | - | - | - | | 123 | 2022-08-23-10:53:04 | my-button-test | 0 | | 123 | 2022-08-23 10:53:06 | my-button-test | 1 | This indicates that for some reason your identifier type (in this case `user_id`) is being tracked with multiple values of `variation_id` and it is impossible to tell which variation to assign to that user. This can happen if: * Your SDK is misaligned with GrowthBook's identifier types for some reason. For example, it's possible that the identifier type you're using as the hash attribute in the SDK is different from the one you're firing in the `trackingCallback` to your warehouse. Ensure that the id you're using to hash users is the same one that you see in your data warehouse for a given user. * If you're using something other than GrowthBook for experiment assignment, there are many possible reasons for this issue: * A bug with the third party solution's hashing algorithm * A mismatch between the identifier type that you have set up in GrowthBook and the ID that is being used to assign variations in that third-party solution * A mistake in your Experiment Assignment Query that is returning the wrong `variation_id` or identifier type for a given experiment. ### Health Tab > [!TIP] > > To get access to the health tab, you need to enable us to run one additional query per experiment analysis. You can do this on a health tab for any experiment if you have the requisite permissions to run queries. The health tab provides you with more insights on the traffic to your experiment over time and across dimensions. ![Experiment Health Tab](https://mintcdn.com/growthbook-ea15456d/4KUzUtpaNUGa50Xc/static/images/health-tab.png?fit=max\&auto=format\&n=4KUzUtpaNUGa50Xc\&q=85\&s=a79552fcd42df8d4449ce16b7ae07a1e) **Experiment Traffic** - A plot of experiment units by the first date they were exposed to the experiment. You can look at daily traffic or cumulative traffic. If you have your experiment dimensions configured with pre-defined slices, we will also return traffic splits by dimension in this tab. For example, if you have a `browser` dimension with pre-defined slices, we will show you the traffic splits by browser. **Experiment Balance Check** - A table with information on the actual number of experiment units, the expected number, and the differences between the percent traffic allocated to each bucket. We also provide you with checks by any pre-defined dimension slices you have configured for your experiment dimensions. #### Adding Dimensions to Health Tab Your health tab shows dimension breakdowns only for Experiment Dimensions which have pre-defined slices. When setting up the health tab, you will be prompted to configure your Experiment Dimensions to have pre-defined slices that can be used in the health tab. This is optional, but we require pre-defined slices for your experiment dimensions to compute dimension traffic and health checks so that we can run only one additional query per analysis and get reliable results. If you want to refresh your dimension slices or change your dimension definitions, you should do so via the Data Source page for the related data source. See [experiment dimensions](/app/dimensions#experiment-dimensions). ## Sharing Experiment Insights Share insights for A/B tests or bandits in 2 ways: Share the full experiment or create a custom report. ![Overflow menu with sharing options highlighted](https://mintcdn.com/growthbook-ea15456d/J3C3juKhu0f7KEr_/static/images/reports-share-menu.webp?fit=max\&auto=format\&n=J3C3juKhu0f7KEr_\&q=85\&s=afb4ae859fbd7337f0ddedada802a57d) ### Sharing a Full Experiment Share a live view of your experiment that automatically updates with the latest data. Viewers can explore the experiment details, variations, and metrics without making changes. ![Share experiment modal](https://mintcdn.com/growthbook-ea15456d/J3C3juKhu0f7KEr_/static/images/reports-share-modal.webp?fit=max\&auto=format\&n=J3C3juKhu0f7KEr_\&q=85\&s=fb93060bf34bd4be332740a8c526d231) To share an experiment: * Go to the experiment page * Click the overflow menu (⋮) * Select **Share Experiment** * Choose **View access** * **Only organization members:** Available to anyone in your organization * **Anyone with the link:** Available via public link > [!TIP] > > Changing access from **Anyone with the link** to **Only organization members** will invalidate existing public links. ### Creating a Custom Report Create point-in-time snapshots to capture specific analyses or time periods. Custom reports are ideal for documenting important findings or sharing focused analyses. To create a shareable custom report: * Go to the experiment page * Click the overflow menu (⋮) * Click **Create shareable report** * Click **Edit Analysis** to customize content * To edit report name and description, click the overflow menu (⋮) → **Edit Name & Description** * Click **Share** * Choose **View access** * **Only me**: Private report visible only to you. * **Only organization members**: Visible to your organization. * **Edit access**: Allow specific organization members to modify the report Reports can be manually updated to reflect current data or any specific date range. ### Managing Custom Reports Access your reports from the **Custom Reports** section in the experiment's **Results** tab. ![Manage custom reports via the Results tab](https://mintcdn.com/growthbook-ea15456d/J3C3juKhu0f7KEr_/static/images/reports-list.webp?fit=max\&auto=format\&n=J3C3juKhu0f7KEr_\&q=85\&s=819a5884acd4c798eefd30836be96d68) From here you can: * View existing reports * Edit report contents * Manage sharing permissions * Delete reports ## Fetching Results via the API To pull results into your own dashboards or data pipeline instead of sharing them from the app, call the [Get results for an experiment](/api/experiments/operation/getExperimentResults) endpoint. It returns the latest analysis for the experiment, including per-variation metric results. # Experiment Time Series Source: https://docs.growthbook.io/app/experiment-time-series Read cumulative experiment results over time in the metric drilldown, including relative, absolute, and scaled impact views. The time series chart shows the cumulative results of your experiment. It's accessible from the [Metric Drilldown](/app/experiment-results#metric-drilldown) available from the experiment results view. Click on the row for the metric whose time series you want to see. Each data point represents cumulative results for that day. When there are multiple updates within a day, GrowthBook consolidates them into a single daily point to keep the chart easy to read. ![Time Series inside of a Metric Drilldown](https://mintcdn.com/growthbook-ea15456d/giSZyh24PfnTkKz4/static/images/experiment-results/metric-drilldown-time-series.png?fit=max\&auto=format\&n=giSZyh24PfnTkKz4\&q=85\&s=a0073e9090c25812db89d3100c616a0e) Selecting a difference type (Relative, Absolute, Scaled Impact) will change the y-axis of the time series to the relevant effect. Selecting a different baseline variation will hide the time series as they are only computed for the default baseline variation. ## FAQ ### Why is some part of the time series dotted? A dotted line in the time series indicates a period when metric settings or analysis settings were different from the current settings or results. Because older data may not be directly comparable, the dotted line shows historical results where comparability is less certain. (A solid line indicates results that are immediately comparable.) ![Dotted part of the time series](https://mintcdn.com/growthbook-ea15456d/giSZyh24PfnTkKz4/static/images/experiment-time-series-dotted.png?fit=max\&auto=format\&n=giSZyh24PfnTkKz4\&q=85\&s=f93c5c89f9857f0d1b32f1a3840bbcbc) ### Why aren't the intervals between data points not equal? The time series reflects experiment refreshes, not fixed time intervals. If updates occur irregularly, the spacing between points will vary. ### Why does the time series only go back so far? The time series will only go back as far as the oldest experiment update (and as far back as late March 2025, when the time series feature was launched). If you have not manually refreshed the experiment results, then the time series will only go back to the last time the experiment was refreshed. Furthermore, if you add a metric mid-experiment, the time series will only show the history of the metric as long as the metric has been in the experiment. # Feature Flags Source: https://docs.growthbook.io/app/features Control application behavior with feature flags: target users, roll out gradually, and run A/B tests without a new deploy. Feature flags let you control your application's behavior from GrowthBook, without deploying new code. Target specific users, gradually roll out changes, or run A/B tests on the client or the server. Feature flags decouple deploys from releases. Release code more frequently and with less risk by launching to a subset of users, ramping up gradually, or turning any change into an experiment. ![Features Overview](https://mintcdn.com/growthbook-ea15456d/1rsmujQCDzXz2Vho/static/images/features/features-overview.png?fit=max\&auto=format\&n=1rsmujQCDzXz2Vho\&q=85\&s=3ee869d40fb640f1ad202b6de4bb9e96) ## Getting Started To use feature flags, set up an [SDK Connection](/lib/quickstart), then explore the docs below. ### Learn the Basics * [Fundamentals](/features/basics) — Keys, feature types, default values, and publishing * [Environments](/features/environments) — Control which features are enabled per environment (dev, staging, production) * [Constants](/features/constants) — Reusable named values you reference from flag values * [Configs](/features/configs) — Typed, validated, composable object shapes you can back JSON flags with (Enterprise) ### Set Up Targeting and Rules * [Rules](/features/rules) — How rules work, choosing a rule type, and configuring forced values, rollouts, experiments, and scheduling * [Targeting Conditions](/features/targeting) — Define who sees a feature using attributes, conditions, and saved groups * [Safe Rollouts](/features/safe-rollouts) — Release features with automatic guardrail monitoring * [Ramp Schedules](/features/ramp-schedules) — Progressively increase traffic on a rule over a defined timeline * [Prerequisites](/features/prerequisites) — Gate features based on the state of other features ### Manage Flags at Scale * [Publishing & Approval Flows](/features/publishing-and-approval-flows) — Drafts, revisions, merge conflicts, and requiring approvals for features and saved groups * [Stale Feature Detection](/features/stale-detection) — Identify flags that may no longer be in use * [Code References](/features/code-references) — Find where flags are used in your codebase * [Feature Diagnostics](/features/diagnostics) — Inspect feature evaluations in production * [Custom Hooks](/features/custom-hooks) — Extend GrowthBook with custom validation logic (self-hosted Enterprise) # Holdouts Source: https://docs.growthbook.io/app/holdouts Hold a percentage of users out of new features so you can measure long-term impact against a true baseline. Enterprise **Holdouts** is available on Enterprise plans. Holdout experiments (or simply "holdouts") measure the long-term impact of features by maintaining a control group that doesn't receive new functionality. While most users experience your latest features and improvements, a small percentage remains on the original version, providing a baseline to measure cumulative effects over time. Learn more about holdouts in our [Knowledge Base](/kb/experiments/holdouts). ## How Holdouts Work in GrowthBook In GrowthBook, units (e.g. users) in the holdout group are withheld from new features, experiments, and bandits. Units not in the holdout group will: participate in experiments, receive feature releases, and see all your latest changes. A subset of these units not in the holdout are used for measurement; these units are the same as the rest of the general population, we just select a sub-sample of units to serve as a comparison group for your holdout population. This approach measures the cumulative impact of all changes over time by comparing the general population to the holdout group. Under the hood, holdouts work by using prerequisites to divert a selected percentage of traffic away from features and experiments towards default values selected for your feature flags. ## Creating a Holdout ![Add Holdout](https://mintcdn.com/growthbook-ea15456d/4KUzUtpaNUGa50Xc/static/images/holdouts-add-modal.png?fit=max\&auto=format\&n=4KUzUtpaNUGa50Xc\&q=85\&s=96e9958706007a190324803709224f12) 1. Navigate to **Experiments** → **Holdouts** and click **Add Holdout** 2. Configure your holdout settings: * **Name**: Choose a descriptive name (e.g., `q1-2025-product-holdout`) * **Holdout Size**: Defaults to 5% (meaning 5% of traffic is placed in the holdout). Note: We also use this % as the amount of traffic in the general population to use for measurement. For example, if you pick 5% for your holdout group, then you will have 5% in the holdout and 95% not in the holdout, but that 95% is split into 5% that are used for measurement and 90% that are not. While using the full 95% for measurement would increase the statistical power of your holdout, it does not represent good statistical value to do so. The statistical power of a test is mostly limited by the size of the smaller group, which in this case is the holdout group, and adding a ton of additional tracking events and ballooning the holdout sample for this (often) long running experiment will cause more events to fire and queries to run longer, often without a meaningful reduction in uncertainty in your test. Please reach out if you wish to implement a different size measurement population. * **Description**: Document the purpose and scope of your holdout * **Projects**: Select the projects in which the holdout should be active. The holdout will be added by default to new features, experiments, and bandits in this project and will only be available to add to these projects. * **Enabled Environments**: Holdouts are automatically added to any linked features in the selected environments (typically, only production). * **Assign Variation by Attribute**: Select the attribute to split traffic into your holdout, typically a user identifier. Note: when you add the holdout to future features or experiments, the holdout will automatically apply to any evaluation of the feature flag and will use the selected attribute to divert traffic to the holdout. * **Goal Metrics**: Select the key metrics for measuring cumulative impact. Note: We prevent adding metrics with conversion windows to a holdout. This is because traffic is exposed to the holdout whenever they hit an included feature or experiment. This can often be days or weeks apart from their exposure to a later feature or experiment, so conversion windows based around their first exposure event no longer make sense. Furthermore, the goal of a holdout is to measure long-range effects; so we allow you to add metrics without conversion windows or with lookback windows. 3. Click **Start Holdout** to begin ## Adding to a Holdout Once your holdout is created, it is selected by default in new feature and experiment creation flows for the selected projects. Whenever someone creates a new feature, experiment, or bandit in a project the holdout was assigned to, a new field for "Holdout" is added to the creation flow and it defaults to the created holdout. If there are multiple holdouts assigned to a project (not recommended), then the feature or experiment creator will have to select the correct holdout to use, as only one can be used at a time per feature or experiment. ### Adding Experiments to a Holdout ![Add Holdout to an experiment](https://mintcdn.com/growthbook-ea15456d/4KUzUtpaNUGa50Xc/static/images/holdouts-experiment-with-holdout.png?fit=max\&auto=format\&n=4KUzUtpaNUGa50Xc\&q=85\&s=d98d4b9635603462374b37315347de08) Once created, you can use your holdout in new experiments: 1. When creating a new experiment, you'll see a **Holdout** field. Note: Experiments created from a feature that has a holdout applied will automatically inherit the holdout from that feature. 2. Your active holdout for the given project will be selected in the dropdown by default 3. Complete the rest of your experiment setup normally The experiment will run as usual, with the holdout tracking long-term cumulative effects in the background. ## Holdout Lifecycle ### Active Period Throughout the active holdout period, you can use the holdout to measure cumulative impact on your metrics as new features and experiments are launched. However, it's often best to measure holdout impact for some set period of time after all new changes are frozen in place and the general population has had a chance to experience them together. For this reason, we make it easy to measure impacts over a set analysis period. During the active period (often a quarter), you'll: * Continue adding new experiments to the holdout * Launch features as normal * Let the holdout accumulate data on cumulative impact ### Analysis Period After your planned duration, transition to the analysis period: 1. **Stop adding new experiments**: The holdout enters analysis mode where no new experiments or features can be added but users are still held out from previously added experiments and features. 2. **Extended measurement**: Continue measuring for 2-4 weeks to capture delayed effects and reduce statistical noise 3. **Dynamic lookback window**: GrowthBook automatically applies a lookback window based on when the analysis window began, ensuring clean measurement During analysis, the system: * Continues to split traffic on features and experiments into your holdout and general population groups if the holdout was already applied. * Applies the appropriate lookback window to exclude results from before the analysis period * Uses the minimum of the metric's lookback window (if it exists) and the analysis lookback window for each metric ### Understanding Holdout Results Holdout results show the cumulative impact of all features and experiments included during the holdout period: * **Positive lift**: Your combined changes improved metrics beyond the baseline * **Negative or neutral lift**: Consider whether individual wins masked broader negative effects * **Metric-specific impacts**: Different metrics may show varying long-term effects The analysis compares your general population (who received all features and were experimented on) against the holdout control group (who remained on the baseline), revealing the true long-term value of your product changes. ## API Reference You can manage the full holdout lifecycle from the [REST API](/app/api): * [Create a holdout](/api/Holdouts/operation/createHoldout), then [list holdouts](/api/Holdouts/operation/listHoldouts), [get a holdout](/api/Holdouts/operation/getHoldout), or [update a holdout](/api/Holdouts/operation/updateHoldout). * [Start the Active Period](/api/Holdouts/operation/startHoldout) to begin holding users out of new features and experiments. * [Start the Analysis Period](/api/Holdouts/operation/startHoldoutAnalysis) once you stop adding new experiments and are ready to measure. * [Stop the holdout](/api/Holdouts/operation/stopHoldout) when the analysis is complete. ## FAQ ### I don't see the holdout when creating my feature or experiment, where is it? Holdouts must be started and must be in the same project as the feature or experiment to be selected in the dropdown. ### Can I have more than one holdout on a feature or experiment? No, you can only have one holdout on a feature or experiment. ### Can I have more than one holdout active at once? Yes, but it is not recommended to have multiple holdouts active at once within a project. If there are more than one available at a time, when creating a new feature or experiment, the user will have to select the correct holdout to use. ### How can I add a Holdout to an existing experiment? Holdouts can only be added to existing experiments if they are (1) in a draft state, (2) no existing linked feature flags, and (3) no existing holdout on the experiment. You can use the three dot menu in the top right of the experiment to add a holdout to the experiment. If there is an existing linked feature flag but the experiment is still in a draft state, you can go to the feature and add the holdout directly to the feature first, and then you can add the holdout to the experiment. ### How can I add a Holdout to an existing feature? Holdouts can only be added to existing features if they have no rules set up in the feature and never have had a linked experiment. You can use the three dot menu in the top right of the feature to add a holdout to the feature. # Importing External Experiments Source: https://docs.growthbook.io/app/importing-experiments Import experiments assigned outside GrowthBook from your warehouse and analyze them with the same metrics and stats engine. GrowthBook can analyze experiments that are run on another service or platform so long as they exist in your linked Data Source. To do this, we create a dedicated experiment in GrowthBook that links to your data warehouse via the experiment metadata. If this is your first time importing any experiments, you need to [connect your data warehouse](/warehouses) and set up your [experiment assignment queries](/app/datasources#experiment-assignment-queries) which will tell GrowthBook where to find the experiment data. Once your datasource is set up, you can import an experiment by clicking select Create Experiment from the Experiment page and then selecting Import. ## Importing Experiments When you choose to Import an Existing Experiment, we scan all of the experiment assignment queries for a datasource and present any experiments we have found in those experiment assignment queries. ![Experiment Import Modal](https://mintcdn.com/growthbook-ea15456d/hKkmiLOwhH6E2xcD/static/images/import-experiment-modal.png?fit=max\&auto=format\&n=hKkmiLOwhH6E2xcD\&q=85\&s=f5437c29c9441393cb2bedbb01e9031a) We scan the experiment assignment queries for any experiment metadata that we can use to create an experiment in GrowthBook. This metadata includes the experiment name, the variations and their splits, and the start date of the experiment. You can choose to Import an experiment, and you will be prompted to confirm this metadata. Because we have to infer the variation splits and the start date from the data we query from the experiment assignment queries, please review the metadata carefully before finalizing the import. ## Missing Experiments If you don't see your experiment in the import list, there are a few potential explanations: 1. The experiment was already imported! You can check the experiments page to see if it was imported, or deselect "Hide Imported" on the importer to see if it shows up there. 2. The experiment does not fit some of the minimum criteria set in the filter bar. You can see that it is possible to filter by minimum number of users and duration. 3. There was a delay in the data being available in your data warehouse. When you refresh the Importer, we only look at data since the last data found by the importer. If there are many experiment assignment queries and one table is more delayed than another, it may cause some experiments not to show up. You can easily remedy this by clicking **Full Refresh** in the bottom right to re-scan the full history of data. 4. The exposure events for your experiment are not in one of the experiment assignment queries for your datasource. Ensure you're selecting the right datasource and that it has the correct query to find your experiment data. # Make Changes to Experiments Source: https://docs.growthbook.io/app/making-experiment-changes Change targeting or rollout on a running experiment without contaminating results, using sticky bucketing when needed. We allow you make changes to an experiment's targeting while it is running. This is especially useful if you want to start running on a small percent of traffic and then gradually ramp up exposure. To make a change to a running experiment, click the "Make Changes" button on the top of the experiment page. You will then be asked what sort of change you want to make, with options ranging from targeting changes to traffic and variation weight changes. You may also make multiple changes at once by choosing "advanced". Based on what you are changing, we will provide a list of possible release plans for that change and provide a recommended plan to use. The UI should guide you to make the correct recommended choice in most cases, but you are able to change this if desired. When changing a **single targeting or traffic setting**, your release plan options may include: * New phase, re-randomize traffic * Same phase, apply changes to everyone * Same phase, apply changes to new traffic only (Sticky Bucketing enabled only) Note that potentially unsafe options per your specific changes may be removed (often "Same phase" options are unsafe for certain types of changes). When using **"advanced"** to make multiple targeting or traffic changes at once, all above options will be made available (including potentially unsafe ones), as well as an additional option: * New Phase, re-randomize traffic, block bucketed users (Sticky Bucketing enabled only) If you are not making targeting or traffic changes and simply want to **start a new phase**, the release plan options will be: * New phase, re-randomize traffic * New phase, do not re-randomize ## Update the existing phase This modifies the experiment in-place without resetting results or re-randomizing users. This provides the best user experience for your users and you can re-use data that was already collected, which can reduce the time the experiment needs to run. However, if you're not careful, this can introduce significant bias and data quality issues into your results. A few categories of changes are broadly considered "safe" and if you are only making these changes, this is the approach we recommend going with. The "safe" changes are: * Increasing the percent of people included in the experiment * Removing a condition from an experiment (e.g. going from "US visitors only" to "All visitors") * Removing an experiment from a namespace All of these changes have something in common - they are simply increasing the number of users exposed to the experiment. There is no effect on existing users in the experiment. If you have Sticky Bucketing enabled, you may also elect to apply changes to new traffic only, leaving already-bucketed users in their existing (sticky) buckets. An example of this scenario could be decreasing the percent of people included in the experiment for all new incoming users, but leaving existing users in their existing buckets. ## Start a new phase This creates a brand new phase of the experiment. All data collected until this point is excluded from the analysis and you start fresh (nothing is deleted from your data warehouse, we just hide old data from the results). In most cases, you will also want to re-randomize traffic. This will cause everyone - including existing experiment users - to get assigned a new random variation. This can be a disruptive user experience since many people will switch from Control to Treatment (or vice versa). Why would you ever want to do this? Some changes you make completely invalidate past results so this lets you cleanly separate the data analysis from before and after you make the change. Re-randomizing traffic can also eliminate [carryover bias](/kb/experiments/carryover-bias) and make your results more reliable and accurate. We recommend this approach for any change that is not considered "safe" (listed above). This can include (but not limited to): * Changing the traffic split (weights) between variations * Adding a new targeting condition * Decreasing the percent of people included Because of how disruptive this can be, it's best to plan ahead before starting an experiment. It's better to start with more conservative targeting and scale up than the reverse (starting big and scaling back). As before, if you have Sticky Bucketing enabled, you have some additional options about how to treat already-bucketed users. By default when starting a new phase, these bucketed users will be reassigned (their sticky bucket will be cleared). You may instead choose to block these users from the experiment going forward (in which case they will still see the control). This strategy is only available in the "advanced" mode. # Managed Warehouse Source: https://docs.growthbook.io/app/managed-warehouse Get started with GrowthBook's fully managed data warehouse. One-click setup, built-in event tracking, and auto-created metrics. GrowthBook Cloud offers a fully managed data warehouse and event tracking pipeline. Provision it in one click, send events from your app, and GrowthBook handles the rest. No infrastructure setup required. > [!NOTE] > > **Not sure if Managed Warehouse is right for you?** > > See [Choose Your Data Path](/app/choose-data-path) for a side-by-side comparison with connecting your own warehouse. ## What you get When you provision the Managed Warehouse, GrowthBook automatically sets up your data source with everything pre-configured: ### Tables We create 3 tables in a ClickHouse database managed by GrowthBook: | Table | What it stores | Indexed by | | - | - | - | | **events** | All custom events (page views, purchases, clicks, etc.) | `event_name`, `timestamp` | | **experiment\_views** | Experiment exposure events | `experiment_id`, `timestamp` | | **feature\_usage** | Feature flag evaluation events | `feature`, `timestamp` | ### Fact Table An **Events** fact table is pre-built on top of the `events` table. This is the base you use to define metrics. It includes 16+ standard columns (timestamp, event name, device ID, geo and browser data, etc.) plus an `attributes` JSON column with everything your events send, and is ready to use immediately. ### Starter metrics Three metrics are auto-created to get you started: | Metric | Type | What it measures | | - | - | - | | **Page Views per User** | Mean | Average number of page views | | **Sessions per User** | Mean | Average number of sessions | | **Pages per Session** | Ratio | Page views divided by sessions | You can edit these or create your own. Read more about [metrics and fact tables](/app/metrics). ### Default identifiers and dimensions Identifiers are the columns used to split traffic in experiments, and dimensions let you slice and dice results. These are set up out of the box: * **Identifiers:** `user_id`, `device_id` * **Dimensions:** `geo_country`, `ua_browser`, `ua_os`, `ua_device_type`, `utm_source`, `utm_medium`, `utm_campaign` You can add more identifiers through your organization's attribute settings. See [Identifiers and custom attributes](#identifiers-and-custom-attributes) for details. ## Getting started ### 1. Provision the Managed Warehouse Go to **Metrics and Data** → **Data Sources** and click **Create** on the Managed Warehouse option. GrowthBook provisions your ClickHouse database, tables, fact table, and starter metrics in seconds. ### 2. Install the SDK with tracking Add the GrowthBook SDK to your application with the tracking plugin enabled. See [Sending events with SDKs](#with-sdks) for setup instructions for each supported language, or use the [Ingestion API](#ingestion-api) for anything else. > [!NOTE] > > **Using eu-west-1?** > > Events default to the `us-east-1` ingestor. If you selected **eu-west-1** as your Data Region, set the ingestor host override shown for your SDK (see [Sending events to the right region](#sending-events-to-the-right-region)), otherwise your events won't reach your warehouse. ### 3. Verify events are flowing Open the **SQL Explorer** in GrowthBook and run a quick query to confirm events are arriving: ```sql theme={null} SELECT event_name, COUNT(*) AS count FROM events WHERE timestamp >= now() - INTERVAL 1 HOUR GROUP BY event_name ORDER BY count DESC ``` You should see your events appear within seconds of sending them. ### 4. Create your first metric Your auto-created metrics (Page Views per User, Sessions per User, Pages per Session) are ready to use immediately. To create a custom metric: 1. Go to **Metrics and Data** → **Fact Tables**. 2. Open the **Events** fact table. 3. Click **Add Metric** and configure it (for example, a Proportion metric filtered to `event_name = 'Purchase'` to track purchase conversion rate). Read more about [metric types and configuration](/app/metrics). ### 5. Run your first experiment With events flowing and metrics defined, you're ready to experiment: 1. Create a feature flag in **Features**. 2. Add an **Experiment** rule to it. 3. Start the experiment and watch results flow in on the **Results** tab. For a detailed walkthrough, see our [guide to running Feature Flag Experiments](/features/feature-flag-experiments). *** ## Benefits * **Fully managed**: No infrastructure management, scaling, or maintenance. * **Seamless integration**: One-click setup + built-in tracking in our SDKs. * **Instant data**: Events are enriched and available within seconds. * **Raw SQL access**: Use the SQL Explorer to run custom queries against your data. ## How it works We use ClickHouse, a database optimized for real-time analytics, to store your event data. The process is: 1. You send analytics events to our scalable ingestion API. 2. We enrich and store them in ClickHouse within seconds. 3. You can query the data with SQL, define metrics, and analyze experiment results with our stats engine. ## Sending events There are 2 ways to send events to GrowthBook Cloud's Managed Warehouse: 1. With our [SDKs](#with-sdks). 2. With our [Ingestion API](#ingestion-api). ### With SDKs These SDKs have a built-in plugin that automatically sends feature usage and experiment view events to GrowthBook, with the SDK's attributes as context. Don't include sensitive information in your attributes (or anonymize it first). For other SDKs, use the Ingestion API below. > [tab] HTML Script Tag > > Add `data-tracking="growthbook"` to the script tag. It reports events that already fired; it does not enroll users in an experiment. > > ```html theme={null} > > ``` > > Push custom events to the `window.gbEvents` array: > > ```html theme={null} > > ``` > > | Attribute | Description | > | - | - | > | `data-event-ingestor-host` | Ingestor for your Data Region. | > | `data-event-transport` | `auto` (default), `beacon`, or `fetch`, like the JavaScript SDK's `transport` option. | > [tab] JavaScript / React > > Requires `@growthbook/growthbook` v1.4.0 or later. We recommend also adding `autoAttributesPlugin` to include common attributes (browser, `session_id`, UTM parameters, etc.) with every event. > > ```js theme={null} > import { GrowthBook } from "@growthbook/growthbook"; > import { > autoAttributesPlugin, > growthbookTrackingPlugin, > } from "@growthbook/growthbook/plugins"; > > const gb = new GrowthBook({ > clientKey: "YOUR_CLIENT_KEY", > plugins: [ > autoAttributesPlugin(), > growthbookTrackingPlugin({ > ingestorHost: "https://us-east-1.gb-ingest.com", > }), > ], > }); > ``` > > Log custom events with `logEvent`. In React, get the instance with `useGrowthBook()`. > > ```js theme={null} > // Without properties > gb.logEvent("Page View"); > > // With properties > gb.logEvent("Button Click", { button: "Sign Up" }); > ``` > > | Option | Description | > | - | - | > | `ingestorHost` | Ingestor for your Data Region. | > | `enableFeatureUsageEvents` | Set to `false` to stop sending feature usage events. Default `true`. v1.8.0+. | > | `transport` | `auto` (default), `beacon`, or `fetch`. See below. v1.7.0+. | > > Events are batched and sent with `fetch` using `keepalive`. When the page is hidden or unloading, the queue is flushed with `navigator.sendBeacon`. Both survive the page being torn down, so exposures aren't lost when a user navigates away immediately, which matters most for redirect experiments. Set `transport: "beacon"` to always prefer `sendBeacon`, or `"fetch"` to never use it. > [tab] Node.js > > Requires `@growthbook/growthbook` v1.7.0 or later. > > ```js theme={null} > import { GrowthBookClient } from "@growthbook/growthbook"; > import { growthbookTrackingPlugin } from "@growthbook/growthbook/plugins"; > > const gb = new GrowthBookClient({ > clientKey: "YOUR_CLIENT_KEY", > plugins: [ > growthbookTrackingPlugin({ > ingestorHost: "https://us-east-1.gb-ingest.com", > }), > ], > }); > ``` > > Log custom events with `logEvent`, passing the user context: > > ```js theme={null} > gb.logEvent("Sign Up", { accountPlan: "pro" }, userContext); > ``` > > User-scoped instances (for example `req.growthbook` from the [Express middleware](/lib/node#express-middleware)) don't need the user context: > > ```js theme={null} > req.growthbook.logEvent("Sign Up", { accountPlan: "pro" }); > ``` > > Set `enableFeatureUsageEvents: false` (v1.8.0+) to stop sending feature usage events. > [tab] Python > > Requires `growthbook` v2.2.0 or later, plus the `requests` package: > > ```bash theme={null} > pip install growthbook requests > ``` > > ```python theme={null} > from growthbook import GrowthBook, GrowthBookTrackingPlugin > > gb = GrowthBook( > client_key="YOUR_CLIENT_KEY", > attributes={"id": "user_123"}, > plugins=[ > GrowthBookTrackingPlugin( > ingestor_host="https://us-east-1.gb-ingest.com", > ) > ], > ) > gb.load_features() > ``` > > Log custom events with `log_event`: > > ```python theme={null} > gb.log_event("Button Click", {"button": "Sign Up"}) > ``` > > Call `gb.destroy()` when you're done with the instance to flush pending events. > > | Option | Description | > | - | - | > | `ingestor_host` | Ingestor for your Data Region. | > | `track_feature_evaluated` | Set to `False` to stop sending feature usage events. Default `True`. | > | `batch_size` | Events to queue before sending. Default `10`. | > | `batch_timeout` | Seconds to wait before sending a partial batch. Default `10.0`. | > > > [!NOTE] > > > > Use the `GrowthBook` class. The async `GrowthBookClient` does not send events to GrowthBook yet. > [tab] PHP > > Requires `growthbook/growthbook` v2.4.0 or later. > > ```php theme={null} > use Growthbook\Growthbook; > use Growthbook\GrowthBookTrackingPlugin; > use Growthbook\GrowthBookTrackingPluginConfig; > > $growthbook = Growthbook::create() > ->withAttributes(['id' => $userId]) > ->addPlugin(new GrowthBookTrackingPlugin( > new GrowthBookTrackingPluginConfig( > ingestorHost: "https://us-east-1.gb-ingest.com", > ) > )); > > $growthbook->initialize("YOUR_CLIENT_KEY"); > ``` > > Call `addPlugin()` after any `with*()` methods. Those return a copy of the instance, and a discarded copy closes the plugin. The plugin gets its client key from `initialize()`, and pending events are sent when the request ends. > > Custom events aren't supported yet. Send them with the [Ingestion API](/app/managed-warehouse#ingestion-api). > > | Option | Description | > | - | - | > | `ingestorHost` | Ingestor for your Data Region. | > | `batchSize` | Events to queue before sending mid-request. Default `100`. | > [tab] Go > > Requires `github.com/growthbook/growthbook-golang` v0.5.0 or later. > > ```go theme={null} > import ( > "context" > > gb "github.com/growthbook/growthbook-golang" > ) > > client, err := gb.NewClient( > context.Background(), > gb.WithClientKey("YOUR_CLIENT_KEY"), > gb.WithSseDataSource(), > gb.WithGrowthBookTracking(gb.TrackingPluginConfig{ > IngestorHost: "https://us-east-1.gb-ingest.com", > }), > ) > if err != nil { > // handle error > } > defer client.Close() // sends pending events > ``` > > Log custom events with `LogEvent`, on a client scoped to the current user. Don't call `Close()` on that client; it closes the shared plugin. > > ```go theme={null} > userClient, _ := client.WithAttributes(gb.Attributes{"id": "user-123"}) > > userClient.LogEvent(context.Background(), "Button Click", gb.EventProperties{"button": "Sign Up"}) > ``` > > | Option | Description | > | - | - | > | `IngestorHost` | Ingestor for your Data Region. | > | `BatchSize` | Events to queue before sending. Default `100`. | > | `BatchTimeout` | How long to wait before sending a partial batch. Default `10s`. | > [tab] Java > > Requires the GrowthBook Java SDK v0.11.1 or later. > > ```java theme={null} > import growthbook.sdk.java.multiusermode.GrowthBookClient; > import growthbook.sdk.java.multiusermode.configurations.Options; > import growthbook.sdk.java.multiusermode.configurations.UserContext; > import growthbook.sdk.java.plugin.tracking.GrowthBookTrackingPlugin; > import growthbook.sdk.java.plugin.tracking.TrackingPluginConfig; > import java.util.Collections; > > String clientKey = "YOUR_CLIENT_KEY"; > > GrowthBookTrackingPlugin trackingPlugin = GrowthBookTrackingPlugin.of( > TrackingPluginConfig.builder() > .clientKey(clientKey) > .ingestorHost("https://us-east-1.gb-ingest.com") > .build() > ); > > GrowthBookClient client = new GrowthBookClient( > Options.builder() > .apiHost("https://cdn.growthbook.io") > .clientKey(clientKey) > .plugins(Collections.singletonList(trackingPlugin)) > .build() > ); > > if (!client.initialize()) { > client.shutdown(); > throw new IllegalStateException("GrowthBook initialization failed"); > } > > UserContext user = UserContext.builder() > .attributesJson("{\"id\":\"user-123\"}") > .build(); > > boolean enabled = client.isOn("my-feature", user); > ``` > > The plugin automatically sends feature usage and experiment view events, with the user's attributes as context. The plugin doesn't inherit the client's SDK connection key, so pass it to both `TrackingPluginConfig` and `Options`. > > Keep one client for your application and create a separate tracking plugin for each client. At application shutdown, call `client.shutdown()` to flush pending events and release resources: > > ```java theme={null} > client.shutdown(); > ``` > > Custom events aren't supported yet. Send them with the [Ingestion API](/app/managed-warehouse#ingestion-api). > > | Option | Description | > | - | - | > | `ingestorHost` | Ingestor for your Data Region. | > | `batchSize` | Events to queue before sending. Default `100`. | > | `batchTimeout` | How long to wait before sending a partial batch. Default `Duration.ofSeconds(10)`. | > | `closeTimeout` | Time budget for flushing events during shutdown. Default `Duration.ofSeconds(5)`. | > [tab] Kotlin > > Requires the GrowthBook Kotlin SDK v7.8.0 or later, with `NetworkDispatcherKtor` v1.2.0+ or `NetworkDispatcherOkHttp` v1.1.0+. > > ```kotlin theme={null} > import com.sdk.growthbook.GBSDKBuilder > import com.sdk.growthbook.network.GBNetworkDispatcherKtor > import com.sdk.growthbook.plugin.TrackingPluginConfig > import com.sdk.growthbook.plugin.tracking.GrowthBookTrackingPlugin > > val networkDispatcher = GBNetworkDispatcherKtor() > > val growthBook = GBSDKBuilder( > apiKey = "YOUR_CLIENT_KEY", > apiHost = "https://cdn.growthbook.io/", > attributes = attributes, > networkDispatcher = networkDispatcher, > trackingCallback = { _, _ -> }, > ) > .setPlugins( > listOf( > GrowthBookTrackingPlugin( > TrackingPluginConfig( > clientKey = "YOUR_CLIENT_KEY", > networkDispatcher = networkDispatcher, > ingestorHost = "https://us-east-1.gb-ingest.com", > ) > ) > ) > ) > .initialize() > ``` > > The plugin doesn't inherit the builder's client key or network dispatcher, so pass both to `TrackingPluginConfig`. Without a network dispatcher, no events are sent. > > Custom events aren't supported yet. Send them with the [Ingestion API](/app/managed-warehouse#ingestion-api). > > | Option | Description | > | - | - | > | `ingestorHost` | Ingestor for your Data Region. | > | `batchSize` | Events to queue before sending. Default `100`. | > | `batchTimeout` | How long to wait before sending a partial batch. Default `10.seconds`. | > [tab] Swift > > Requires the GrowthBook Swift SDK v1.1.15 or later. > > ```swift theme={null} > import GrowthBook > > let gb: GrowthBookSDK = GrowthBookBuilder( > apiHost: "https://cdn.growthbook.io", > clientKey: "YOUR_CLIENT_KEY", > attributes: ["id": "user-123"], > trackingCallback: { _, _ in } > ) > .addPlugin(GrowthBookTrackingPlugin( > config: .init(ingestorHost: "https://us-east-1.gb-ingest.com") > )) > .initializer() > ``` > > Custom events aren't supported yet. Send them with the [Ingestion API](/app/managed-warehouse#ingestion-api). > > | Option | Description | > | - | - | > | `ingestorHost` | Ingestor for your Data Region. | > | `batchSize` | Events to queue before sending. Default `100`. | > | `batchTimeout` | Seconds to wait before sending a partial batch. Default `10`. | #### Sending events to the right region The tracking plugin sends events to `https://us-east-1.gb-ingest.com` by default. If you selected **eu-west-1** as your Data Region when creating your Managed Warehouse, set the ingestor host option shown for your SDK above to `https://eu-west-1.gb-ingest.com`. Otherwise events won't reach the right ClickHouse cluster and will be dropped. You can find your datasource's configured region on its settings page. ### Ingestion API You can also send events directly to our ingestion API. The host depends on the **Data Region** you chose when creating your Managed Warehouse datasource: * `us-east-1` → `https://us-east-1.gb-ingest.com` * `eu-west-1` → `https://eu-west-1.gb-ingest.com` Sending events to the wrong region's host means they won't reach the ClickHouse cluster your data warehouse actually lives in — always match the host to the region shown on your datasource's settings page. Pass an array of event objects, each with the following properties: * **event\_name**: The name of the event (e.g., "Purchase", "Button Click") * **properties**: Optional key-value pairs with properties of the event itself * **attributes**: Optional key-value pairs with attributes of the user or context at the time of the event * **timestamp**: Optional ISO timestamp of when the event occurred. Only used when batching events with a `sentAt` field (see [Event timestamps](#event-timestamps) below). ```bash theme={null} curl -X POST "https://YOUR_WAREHOUSE_REGION.gb-ingest.com/track?client_key=YOUR_CLIENT_KEY" \ -H "Content-Type: application/json" \ -d '[{ "event_name": "Purchase", "properties": { "amount": 100 }, "attributes": { "user_id": "12345" } }]' ``` Make sure you do not include any sensitive information in your events (or if you do, anonymize it first). #### Identifiers To tie events to users for experiment analysis, include identifier keys in `attributes`: * **`user_id`**: a logged-in user ID. Promoted to the top-level `user_id` column at ingest. * **`device_id`**: an anonymous device or browser ID. Promoted to the top-level `device_id` column at ingest. * **`anonymous_id`** or **`id`**: also resolve to the `device_id` identifier, but at analysis time instead of ingest — they stay inside the `attributes` JSON. Prefer the explicit `user_id` and `device_id` keys, and use each key for one kind of ID consistently. For example, if you send a logged-in user ID as `id` today and later start sending a real device ID as `device_id`, the `device_id` identifier will mix the two ID spaces and experiment analysis will silently drop units. See [Identifiers](#identifiers-and-custom-attributes) for how these keys map to identifiers. #### Event timestamps By default, events are timestamped with the time they are received by the ingestion API. If you batch events before sending, you can preserve each event's original timing by adding a per-event `timestamp` along with a top-level `sentAt` field: ```bash theme={null} curl -X POST "https://YOUR_WAREHOUSE_REGION.gb-ingest.com/track?client_key=YOUR_CLIENT_KEY" \ -H "Content-Type: application/json" \ -d '{ "sentAt": "2025-06-01T12:00:05.000Z", "events": [{ "event_name": "Purchase", "timestamp": "2025-06-01T12:00:00.000Z", "properties": { "amount": 100 }, "attributes": { "user_id": "12345" } }] }' ``` * **sentAt**: ISO timestamp of when the request was sent, from the same clock used for the event timestamps * **timestamp**: ISO timestamp of when the event occurred. Must be earlier than `sentAt`, otherwise it is ignored. We don't trust client timestamps directly. Instead, we subtract the difference between `sentAt` and each event's `timestamp` from our own server time when the request is received. This preserves the relative timing of your events while correcting for client clock skew. #### Experiment view events In order to use GrowthBook's experiment analysis features, you must send an event every time a user views an experiment. It must match the following format: * **event\_name**: Must be `"Experiment Viewed"` * **properties**: Must include the following key/value pairs: * `experimentId`: The ID of the experiment being viewed * `variationId`: The ID of the variation that was shown to the user In addition, you must include `attributes` with the user attributes that were used to evaluate the experiment, plus any attributes you want to use as dimensions for slicing and dicing. At minimum this must include the attribute the experiment hashes on (its **Assign Variation by Attribute**), since that's the [identifier](#identifiers) used to join exposures to your metrics. #### Feature usage events To take advantage of GrowthBook's feature usage analytics, you must send an event every time a feature is evaluated with a specific format. * **event\_name**: Must be `"Feature Evaluated"` * **properties**: Must include the following key/value pairs: * `feature`: The name of the feature being evaluated * `value`: The feature's value that was returned from the evaluation * `source`: (optional) The source of the feature value (e.g., "defaultValue", "experiment", "force") * `ruleId`: (optional) The ID of the specific rule that was used to evaluate the feature (or `$default` if the default value was used) * `variationId`: (optional) If the value came from an experiment, the ID of the variation that was returned #### Attributes Attributes are key-value pairs that provide context about the user or environment at the time of the event. They can be used to slice and dice your data in analysis. It's recommended to include the same attributes you use in your GrowthBook SDK. Some attributes are automatically enriched by the ingestion API: * `ip`: a geoip lookup is done and the following attributes are added. If an ip attribute is not provided, we use the IP address of the request. * `geo_country` * `geo_city` * `geo_lat` * `geo_lon` * `ua`: the user agent is parsed and the following attributes are added. If a user agent attribute is not provided, we use the user agent of the request. * `ua_browser` (e.g. Safari) * `ua_os` (e.g. macOS) * `ua_device_type` (e.g. mobile) * `url`: the URL is parsed and the following attributes are added: * `url_path` (e.g. /products/123) * `url_host` (e.g. [www.example.com](http://www.example.com)) * `url_query` (e.g. ?utm\_source=google) * `url_fragment` (e.g. #section1) It's also recommended to include attributes about which SDK is being used. This helps with debugging. * `sdk_language` (e.g. python) * `sdk_version` (e.g. 1.2.3) #### Limits When calling the ingestion API directly, be aware of the following default limits: * Maximum of 100 events per request. * Maximum of 1 request per second. If you need to send more than this, reach out and we can increase your limits. ## Identifiers and custom attributes All attributes sent with your events are stored in a native ClickHouse JSON column called `attributes` on the `events` table (event properties live in a `properties` JSON column). Every attribute is automatically queryable — no setup required. Identifiers — the attributes used to split traffic in experiments — come in two flavors: ### Built-in identifiers Every table has `user_id` and `device_id` columns. The columns always exist, but they only carry values when your events include one of the following attribute keys: | Identifier | Populated from attribute keys (first non-empty wins) | Intended for | | - | - | - | | `user_id` | `user_id` | Logged-in user IDs | | `device_id` | `device_id`, `anonymous_id`, `id` | Anonymous, device, or session IDs | The SDK tracking plugin applies this mapping automatically from your SDK attributes, and the [Ingestion API](#identifiers) does the same for the `user_id` and `device_id` keys. Events that carried their ID as `anonymous_id` or `id` are resolved into `device_id` when queries run, so experiment analysis works with any of these keys — including for past events. When you assign an experiment by hash attribute, GrowthBook picks the matching identifier automatically: experiments assigned on the `id` or `anonymous_id` attribute use the `device_id` identifier for analysis. ### Custom identifiers Any other scalar attribute marked as an **Identifier** under **SDK Connections** → **Attributes** (for example `account_id`) is automatically exposed as a top-level column on your fact tables and available for experiment assignment. > [!NOTE] > > Identifier resolution reads from the `attributes` JSON column, so marking an attribute as an identifier applies retroactively to all past events that included it. ## SQL Explorer You can use the SQL Explorer to run ad-hoc queries against your events. This is useful for exploring your data, debugging issues, or creating custom reports. > [!NOTE] > > The SQL Explorer only allows read-only SELECT queries. Write operations (INSERT, UPDATE, etc.) are prevented from being executed by the platform. You will see 3 tables in the SQL Explorer: * **feature\_usage**: contains all feature usage events * **experiment\_views**: contains all experiment view events * **events**: contains all other events ### SQL best practices #### Use indexes For best performance, take advantage of the indexed columns in each table: * **events** table: * `timestamp`: the time the event occurred * `event_name`: the name of the event (e.g., "Purchase", "Button Click") * **feature\_usage** table: * `timestamp`: the time the event occurred * `feature`: the name of the feature being evaluated * **experiment\_views** table: * `timestamp`: the time the event occurred * `experimentId`: the ID of the experiment being viewed #### Querying attributes and properties The `attributes` and `properties` columns are native ClickHouse JSON columns, so you can access fields inside them with dot syntax. Cast the value to get a concrete type: ```sql theme={null} SELECT attributes.account_plan::Nullable(String) AS account_plan, toFloat64OrNull(properties.amount::Nullable(String)) AS amount FROM events WHERE timestamp >= '2025-06-01 00:00:00' AND event_name = 'Purchase' ``` Attributes marked as [Identifiers](#identifiers-and-custom-attributes) are already exposed as top-level columns, so you can reference them directly without the JSON path. #### Identifier columns in custom SQL The physical `user_id` and `device_id` columns only contain values that were promoted at ingest — by the SDK tracking plugin, or by the `user_id`/`device_id` attribute keys on the Ingestion API. Events that carried their ID as `anonymous_id` or `id` keep it inside the `attributes` JSON instead. GrowthBook-generated queries resolve this automatically, but hand-written SQL reads the raw columns. If your custom SQL joins or groups on identifiers — especially in a [custom fact table](/app/metrics) used for experiment analysis — use the same resolution expressions GrowthBook uses: ```sql theme={null} coalesce( nullIf(user_id, ''), nullIf(attributes.user_id::Nullable(String), '') ) AS user_id, coalesce( nullIf(device_id, ''), nullIf(attributes.device_id::Nullable(String), ''), nullIf(attributes.anonymous_id::Nullable(String), ''), nullIf(attributes.id::Nullable(String), '') ) AS device_id ``` Keep the standard column names (`user_id`, `device_id`): experiment analysis joins your fact table to GrowthBook's exposure queries by identifier, so a fact table that extracts an ID from the JSON under a different name won't match any exposures. #### Large queries There is a limit of 1000 rows in the SQL Explorer. Returning raw events over a large time period will quickly exceed this limit. To work around this, use `GROUP BY` in your queries to aggregate results. One common aggregation is by time intervals. You can use the `toStartOf...` functions in ClickHouse for this. For example, to get daily event counts: ```sql theme={null} SELECT toStartOfDay(timestamp) AS day, COUNT(*) AS count FROM events WHERE timestamp >= '2025-06-01 00:00:00' GROUP BY day ORDER BY day ASC ``` Whether you have thousands or millions of events, this query will always return a manageable number of rows (one per day). There are also functions like `toStartOfHour`, `toStartOfMonth`, etc. that you can use to group by different time intervals. ## FAQ ### Can I use the Managed Warehouse alongside my own warehouse? Yes. You can add a separate data source for your own warehouse at any time and use both simultaneously. Note that each experiment pulls data from a single data source, so you can't mix metrics from both warehouses within one experiment. ### How do I add custom identifiers like `account_id`? `user_id` and `device_id` are built in. To add another identifier, go to **SDK Connections** → **Attributes** and mark the attribute as an **Identifier**. Include it in the `attributes` of your events and it will be exposed as a top-level column on your fact tables. ### Why are the `user_id`/`device_id` columns empty when I query directly? Those columns are only filled at ingest when events include the `user_id` or `device_id` attribute keys (the SDK tracking plugin does this for you). If your events carried their ID as `anonymous_id` or `id`, it lives in the `attributes` JSON — experiment analysis resolves it into `device_id` automatically, but your own SQL needs to do the same. See [Identifier columns in custom SQL](#identifier-columns-in-custom-sql). ### What happens if I exceed my event limit? You'll see a notification in GrowthBook when you're approaching your limit. On free plans, events stop being tracked for the remainder of the month and reset the following month. On paid plans, overage charges apply. ### Can I export my data? You can query and export results through the SQL Explorer. ### Can I use the Managed Warehouse when self-hosting? The Managed Warehouse is only available on GrowthBook Cloud. Self-hosted instances should [connect their own data warehouse](/warehouses). # Metrics and Fact Tables Source: https://docs.growthbook.io/app/metrics Define Fact Tables and metrics so GrowthBook can compute experiment results from your warehouse. Metrics are how you measure success and failure for your organization. An e-commerce company might have metrics related to conversion rates, revenue, and cart abandonment. A B2B SaaS company might have metrics around subscription trials, cancellations, and usage. Every company is different and GrowthBook is flexible enough to support a wide variety of use cases. The primary way to create metrics in GrowthBook is with **Fact Tables** where you write SQL to pull a set of rows and then define multiple metrics on top of those rows. There is also a [legacy method](/app/metrics/legacy) where each metric has its own self-contained SQL. Fact Tables are easier to use, faster to query, and more powerful, so we highly recommend using them for all new metrics. If you learn best by example, you can jump straight into [Metric Examples and Use Cases](/app/metrics/examples). ## Fact Tables Fact Tables are defined by a SQL SELECT statement and are the base that metrics are built on top of. At a minimum, a Fact Table must select the following columns: * `timestamp` - The date the event happened * One column per supported identifier type (e.g. `user_id` and `anonymous_id`). The possible identifier types depend on your data source settings. Any other columns you return can be used to create metrics or used as a filter. Here's a full example: ```sql theme={null} SELECT -- Required columns purchase_date as timestamp, user_id, anonymous_id, -- Additional columns (depends on your use case) amount, coupon_code, device_type FROM orders WHERE status = 'completed' ``` This is an example of a "modeled table", which represents a specific order event and has dedicated columns related to orders. GrowthBook also works great with raw event stream tables and even some pre-aggregated tables. Check out [Metric Examples and Use Cases](/app/metrics/examples) for more info and examples of these different data schemas and table types. ### Columns When you create a Fact Table, we query the first few rows and inspect the returned data to determine which columns you've selected and what data types they are. This process isn't perfect. For example, if the first few rows happen to have `null` as the value of a column, we can't tell if it's supposed to be a number, string, etc.. When this happens, we will show you a warning and you can manually specify the type. #### Number Formats For numeric columns, you can also specify a number format, which controls how the metric is displayed on the front-end. Possible values are `currency`, `time:seconds`, `memory:bytes`, and `memory:kilobytes`. > [!NOTE] > > **USD by default** > > The default display currency is USD, but you can change it under **Settings** → **General** → **Metric Settings** To manage these metrics as code, use the Fact Metrics REST API: [create a fact metric](/api/fact-metrics/operation/postFactMetric) or [list existing fact metrics](/api/fact-metrics/operation/listFactMetrics). ## Metric Types There are 7 types of metrics you can create within GrowthBook - Proportion, Retention, Daily Participation, Mean, Ratio, Quantile, and Funnel. ### Proportion Metrics Proportion Metrics measure the percent of users in an experiment who exist in a Fact Table (and match any filters you specify). For example, if you have an Orders Fact Table, you can create a Proportion Metric to see what percent of users purchased something. For this metric, we don't care how many times they purchased, just that they have at least 1 matching row. ### Retention Metrics Retention Metrics are used to measure the retention of users over time with respect to their first experiment exposure. For example, you might want to measure the retention of users who came back to your site at least 7 days after their first experiment exposure. You can use the filters to determine if a user qualifies as retained (using a row filter to only look for certain events or using a user filter to only look for users who completed an action a certain number of times). You then set the retention window, which is the amount of time after the first experiment exposure that a user must do the qualifying actions. You can also use a conversion window to only look for a certain time period after the start of the retention window. For example, if you want to measure the retention of users who came back to your site in the second full week after the experiment exposure, you would set the retention window to 7 days and the conversion window to 7 days. ### Daily Participation Metrics Daily Participation Metrics measure the average percentage of days after experiment exposure that a user is active (i.e., meets the metric criteria). This is analogous to Daily Active Users (DAU) in an experiment context, where you want to measure engagement frequency over time. For each user, GrowthBook: 1. Counts the number of distinct days they match the metric criteria (have rows in the Fact Table that meet your filters) 2. Calculates the number of eligible days since their first experiment exposure (based on the metric window settings) 3. Computes the user's participation rate as: `(matching days) / (eligible days)` 4. Averages these participation rates across all users in the variation The result is displayed as a percentage (0-100%), representing the average daily participation rate for users in that variation. **Example:** If your control variation has two users: * **User 1**: Meets the active criteria on 2 days and has been in the experiment for 5 days → participation rate = 2/5 = 0.4 (40%) * **User 2**: Meets the active criteria on 3 out of 3 days → participation rate = 3/3 = 1.0 (100%) The variation average would be: (0.4 + 1.0) / 2 = 0.7, or **70%**. This means the average daily participation rate is 70% for users in this variation. If you had a conversion or lookback window, the number of eligible days would be limited to the number of days in that window and the participation rate would be calculated based only on that window. **Use Cases:** * Measuring user engagement frequency (e.g., "What percentage of days do users log in?") * Tracking feature adoption over time (e.g., "What percentage of days do users use the new feature?") ### Mean Metrics Mean Metrics are useful when there are more than 2 possible states a user can be in. For example, instead of just purchase/not purchase, you might care about the average number of orders a user makes, the average revenue you earned per user in an experiment, or a count of the distinct types of items a user purchased. For the metric value, there are 4 types of aggregations you can do: * `COUNT(*)` - a count of all rows per user in your Fact Table * `COUNT(DISTINCT DATE(timestamp))` - the distinct number of days user completed the qualifying actions in your fact table (e.g. for a count of active days per user, akin to daily active users \[DAU] metrics) * `SUM(column)` - the sum per user of any numeric column in your Fact Table * `MAX(column)` - the max of a numeric column per user in your Fact Table * `COUNT(DISTINCT column)` - the distinct count of a string column per user in your Fact Table. Note: for `COUNT(DISTINCT column)` where you pass an arbitrary column, we use the [HyperLogLog](https://en.wikipedia.org/wiki/HyperLogLog) estimation method to approximate the distinct count while scaling to account for data with many rows and easily allow re-aggregation. However, `COUNT DISTINCT` is still more computationally costly than other aggregation types, so use it only when necessary. This aggregation is not available for SQL Server, MySQL, Postgres, or Vertica. For example, an **Orders per User** metric would use `COUNT(*)` since you want to count the total number of rows. A **Revenue per User** metric would use `SUM` and select the column where the order total is stored. The denominator for Mean Metrics is always the number of users in an experiment variation. So, even though you may be doing `SUM(revenue)` as the aggregation, it will be divided by number of users and you will actually end up with an average value per user, not an overall sum for the variation. ### Ratio Metrics Ratio Metrics let you pick a custom denominator and allow for metrics such as **Average Order Value** (denominator is number of orders) or **Session Duration** (denominator is number of sessions). For both the numerator and denominator, you can select a Fact Table, an aggregation, and optional filters. GrowthBook allows the numerator and denominator to have different Fact Tables, which can open up some really advanced use cases. The types of aggregations supported for ratio metrics are: * `COUNT(*)` - Number of rows in the Fact Table * `COUNT(DISTINCT 'User Identifier')` - Sample size of the experiment variation * `SUM(column)` - Total of a numeric column in the Fact Table * `MAX(column)` - Max of a numeric column in the Fact Table by user * `COUNT(DISTINCT column)` - Distinct count of a string column in the Fact Table by user Under the hood, GrowthBook uses the Delta Method to accurately determine the variance. For example, to create a **Session Duration** metric, you would do the following: * Numerator: `SUM(duration)` from a `Sessions` fact table * Denominator: `COUNT(*)` from a `Sessions` fact table ### Quantile Metrics Quantile Metrics are useful when you care about comparing variations at a quantile (e.g. latency at P99) rather than the mean. These are also called percentile metrics. Learn more about when to use [Quantile Metrics](/statistics/quantile). One key consideration is `Group by Experiment User before taking quantile?`. In short, if you often think about your metric at the user level, you should aggregate. If you think about your metric at the event level, do not aggregate. For the metric value, the only aggregation available is `SUM(column)`. For `SUM`, you will be able to choose any numeric column in your Fact Table. You have the option to `Ignore Zeros`. For event-level analysis, ignoring zeros entails removing all events from the analysis with value equal to 0. Similarly, for a user-level analysis, removing zeros entails removing all rows corresponding to user-level aggregations that have value 0. When picking `Quantile` you have several default options or can select `Custom`. If you select `Custom` you must input a number between 0 and 1. For example, inputting 0.75 will compare the $75^{\text{th}}$ percentiles across variations. ### Funnel Metrics Pro **Funnel Metrics** is available on Pro and Enterprise plans. Funnel Metrics measure the share of exposed units who complete a series of steps in order, such as viewing a product, adding it to a cart, and then purchasing. Instead of a numerator, you define an ordered list of steps, each with its own Fact Table, row filters, and conversion window. Each step is measured against all exposed units, not just the units who completed the previous step, which keeps every step directly comparable to your other metrics. The metric's own value is the share of units who reach the last step. See [Funnel Metrics](/app/metrics/funnel) for step settings, conversion windows, optional steps, and the API. ## Filters The real power of Fact Tables comes with filtering. This allows you to easily create metric variants. There are 2 kinds of filtering available * Row Filters * User Filters ### Row Filters Row Filters determine which rows of the Fact Table are included in the metric calculation. You can define multiple Row Filters and they will be ANDed together. Row Filters support a wide variety of operators: * `=` * `!=` * `>` * `<` * `>=` * `<=` * `between` (takes two values: a lower and an upper bound) * `not_between` * `in` * `not_in` * `is_null` * `not_null` * `is_true` * `is_false` * `contains` * `not_contains` * `starts_with` * `ends_with` * `matches_pattern` (glob: `*` matches any run of characters, `?` matches one) * `not_matches_pattern` * `sql_expr` (raw SQL for advanced use cases) * `saved_filter` (a saved SQL expression that can be reused across metrics) Filters on date and timestamp columns are compared in UTC. The UI offers `=`, `<`, `<=`, `>`, `>=`, `between`, `not_between`, and `is not null` for these columns, and a value is either a calendar day (`2024-01-15`) or a minute-precision timestamp (`2024-01-15T09:30`). A calendar day matches the whole day rather than the instant at its midnight, so `= 2024-01-15` includes every row stamped on that date. #### Auto Prompting for Row Filters It's common to have an important "type" column that you use to build out metrics. For example, if your Fact Table is a raw event stream, there will be a column like `event_type` to differentiate between a page view, button click, purchase, etc.. You will almost always want to filter metrics using this column. After creating your Fact Table, you can edit a column and check the “Prompt metrics to filter on this column” box. This helps GrowthBook better streamline the UI when creating metrics. ![Marking an Event Type column](https://mintcdn.com/growthbook-ea15456d/hKkmiLOwhH6E2xcD/static/images/metrics/inline-filter.png?fit=max\&auto=format\&n=hKkmiLOwhH6E2xcD\&q=85\&s=88cda3b588595dc8b04eb560b3ccaa9d) When you check this box, GrowthBook will run a quick query looking for the top 100 values for this column in the past week. This will be used to add an auto-complete field in the UI when creating a metric: ![Event Type Filter UI](https://mintcdn.com/growthbook-ea15456d/hKkmiLOwhH6E2xcD/static/images/metrics/event-type-filter-metric-form.png?fit=max\&auto=format\&n=hKkmiLOwhH6E2xcD\&q=85\&s=e972257ee819f91a1af25df25f1f24f8) Another example use of this checkbox is for a `Page Path` column in a `Page Views` Fact Table. This will streamline creation of metrics such as "Visited /checkout", "Visited /pricing", and more. GrowthBook also supports secondary filters. On a broad Events table, after marking the `event_name` column to always filter, you can then choose secondary filter columns based on the selected event. For example, if the user picks the `Form Submit` event, you can have it automatically prompt for the `formId` column. ### User Filters User Filters are applied AFTER aggregating by user. Instead of filtering individual rows, you are filtering the total value per user. You can either filter on the `Count of Rows` or the `SUM` of a numeric column. This can be easier to understand with examples: * Count of rows: `>= 3`: Only include users with at least 3 rows in a Fact Table * SUM of a revenue column: `> 100`: Only include users who have spent at least \$100 total across all of their orders. Only simple SQL comparison operators are supported: `<`, `>`, `<=`, `>=`, `=`, `<>`. User Filters can only be applied to Proportion metrics and the numerator of Ratio metrics (only when using `COUNT(DISTINCT 'User Identifier')`). ## Metric Windows When used in an experiment, we only consider rows of a Metric where the timestamp is greater than or equal to the first time the user was exposed to the experiment. In other words, if someone purchases something before seeing your experiment, it won't be included in the analysis. This behavior is ideal for the vast majority of metrics, but you can change it with the Metric Delay setting if desired (see below). There are three window settings one can use to configure the metric date window. Each of them defines the lower and upper date range of the metric to use for each user: * **None** (default for Fact Metrics) * Lower bound: user's first exposure plus the metric delay * Upper bound: experiment end date * **Conversion Window** (default for Legacy Metrics) * Lower bound: user's first exposure plus the metric delay * Upper bound: the lower bound + the length of the conversion window * **Lookback Window** * Lower bound: the experiment end date minus the lookback window OR the user's first exposure plus the metric delay, whichever is later * Upper bound: experiment end date Here's a graphical representation of these three window types for a hypothetical User 1: ![Metric Windows](https://mintcdn.com/growthbook-ea15456d/hKkmiLOwhH6E2xcD/static/images/metric-windows.png?fit=max\&auto=format\&n=hKkmiLOwhH6E2xcD\&q=85\&s=facb42800cfd4d1b962f83dd9f8d4df6) Here's a second example for a hypothetical User 2, who joins the experiment late. Notice that the conversion window can extend beyond the experiment end date. ![Metric Windows (User 2)](https://mintcdn.com/growthbook-ea15456d/hKkmiLOwhH6E2xcD/static/images/metric-windows-user-2.png?fit=max\&auto=format\&n=hKkmiLOwhH6E2xcD\&q=85\&s=aebb492bd81eb15e3fc3274c1310864e) Why might you choose one window over another? **None** - The simplest. Use all data available. This is useful for using as much data associated with users in your experiment and will combine any behavior that is right after experiment exposure as well as long run behavior within the experiment time frame. **Conversion window** - Conversion windows allow you to only look at events that are tied to the first exposure to an experiment. This can help if, for example, you are tracking purchases and you only want to measure the effect of an experiment in a checkout flow on purchases made soon after seeing that checkout flow. Using a conversion window can reduce the noise from user behavior not related to an experiment. However, if you set the window too short, you may not capture users that return a few days later and were influenced by the experiment. **Lookback window** - Lookback windows are good for capturing long run impacts of an experiment on regular behavior like user log ins or page views. They have two main advantages: (1) You can mitigate the novelty effect of an experiment; if you are testing a new recommendation algorithm, at first users may react a certain way to the experiment, but eventually they may adjust and so may their behavior. In these cases, you may just want to look at the last 14 days of an experiment. (2) Lookback windows help you focus on the long run effects of an experiment. Much of experimentation is about building a better product; by focusing on impact of an experiment after it has been live for a week or two, you may get a better picture of the long run impact of launching the experiment. Larger companies who measure long run logged in behavior want to ensure their experiments have lasting effects and often rely on lookback windows to make shipping decisions. However, lookback windows may not be right if you are testing a feature on logged-out or anonymous users and are measuring simple purchase conversions or something similar. In these cases, you might end up with many logged-out users who, in the long run, simply have no metric data associated with them. ## Advanced Settings ### What is the Goal? For the vast majority of metrics, the goal is to increase the value. But for some metrics like "Bounce Rate" and "Page Load Time", lower is actually better. Setting this to "decrease" basically inverts the "Chance to Win" value in experiment results so that "beating" the control means decreasing the value. This will also reverse the red and green coloring on graphs. ### Capped Value Large outliers can have an outsized effect on experiment results. For example, if your normal revenue per user \$40 and someone happens to make a \$5000 order, whatever variation that person is in will be much more likely "win" any experiment because that one order is an outlier. Capping (also known as winsorization) works by ensuring that all aggregate unit (e.g. user) values are no more than some value. So in the above example, if the cap was \$100, the \$5000 purchase will still be counted, but the aggregated value for that user will be capped at \$100 and will have a much smaller effect on the results. It will still give a boost to whatever variation the person is in, but it won't completely dominate all of the other orders and is unlikely to make a winner just on its own. Another way to think about this is that you are slightly biasing your results by truncating large values, but you are reducing variance to prevent the outsized effect of outliers. There are two ways to cap metric values in GrowthBook: **1. Absolute capping** - if set above zero, all aggregated user values will be capped at exactly this value. For example, if the cap is \$100 on total revenue per user, then after we sum all of a user's orders up, any user with an aggregate sum of greater than \$100 will be set to \$100. **2. Percentile capping** - when this is set to between 0 and 1, it uses that percentile to select a cap based on the data in your experiment so far. This cap is therefore specific to each experiment and specific to each analysis run in that experiment if new data has come in. It works like so: after we calculate the unit-level aggregate values for all units (e.g. users) during an experiment analysis, we find the specified percentile of these unit-level aggregates and then cap these aggregated values at this percentile. Using the above example, if you were to specify percentile capping with a value of `0.95`, then we find the 95th percentile of total revenue per user (say this turns out to be \$135). We then cap those user-level aggregates at \$135. For ratio metrics, capping applies separately to each user's aggregated numerator and denominator, before calculating the ratio of their sums. Absolute capping uses the same numeric bound for both components. Percentile capping uses the same percentile setting but calculates a separate bound for each component. Neither method caps the per-user ratio itself. For example, an orders/cancellations ratio metric with an absolute upper cap of `10` limits each user's total order count to `10` and total cancellation count to `10`. You can additionally choose to ignore zeros, which will compute the percentile without including any user aggregated zero values. This is useful if you have a lot of zero values and you don't want to have to fine tune the percentile to avoid setting the cap too low. Because the percentile cap depends on the data in your experiment, it can be different from experiment to experiment, or even analysis to analysis. To find out what value was actually used for capping you can do the following: on the Experiment Results tab, click the three dot menu in the top right and select "View Queries". Each percentile capped metric will have a column with the `main_cap_value` that was used to cap that metric and represents the computed percentile of unit-level aggregate values. #### Lower-Tail Capping Fact Metrics can also set a lower bound (floor). Absolute floors can be zero or negative; percentile floors must be strictly between `0` and `1`, such as `0.05` for the fifth percentile. When both tails use the same type, the lower value must be less than the upper value. Two percentile tails must use the same **Ignore zeros** setting. Mixed types are allowed: if the computed percentile crosses the absolute bound, the absolute bound takes precedence. In the API, `cappingSettings` controls the upper tail and `lowerCappingSettings` is a separate top-level field: ```json theme={null} { "cappingSettings": { "type": "percentile", "value": 0.99 }, "lowerCappingSettings": { "type": "absolute", "value": 0 } } ``` ### Metric Delay Conversions within the first X hours of being put into an experiment are ignored (default = `0`). This is useful for metrics like "day 2 retention". In that case, if your underlying table reports whether a user is retained on any given day, you could set a metric delay to `24` hours. #### Negative metric delays The metric delay can also be negative to include some conversions **before** a user is put into an experiment. For example, a value of `-2` would mean conversions up to 2 hours before will be included. You might be wondering when this would ever be useful. Imagine the average person stays on your site for 60 seconds and your experiment can trigger at any time. If you just look at the average time spent after the experiment, the numbers will lose a lot of meaning. A value of `20 seconds` might be horrible if it happened to someone after only 5 seconds on your site since they are staying a lot less time than average. But, that same `20 seconds` might be great if it happened to someone after 55 seconds since their visit is a lot longer than usual. Over time, these things will average out and you can eventually see patterns, but you need an enormous amount of data to get to that point. If you set the metric delay to something negative, say `-0.5` (30 minutes), you can reduce the amount of data you need to see patterns. For example, you may see your average go from 60 seconds to 65 seconds. Keep in mind, these two things are answering slightly different questions. `How much longer do people stay after viewing the experiment?` vs `How much longer is an average session that includes the experiment?`. The first question is more direct and often a more strict test of your hypothesis, but it may not be worth the extra running time. ### Bayesian Priors Your organization can set default priors for Bayesian analyses that are used by all metrics. However, you can also set metric specific priors by opening the Edit Metric modal from the Metric page, clicking on Advanced Settings, and turning on the metric override. This will allow you to set a custom prior for that metric. Additionally, you can use experiment metric overrides to further customize these priors for each experiment. You can read more about Bayesian priors on [our statistical details page](/statistics/details). ## Metric Slices Enterprise **Metric Slices** is available on Enterprise plans. Many metrics are easily decomposed, and you may want to learn about the different elements. For example, you may want to see revenue across different product types (e.g. "apparel", "equipment"). Rather than creating and maintaining a separate revenue metric for each product type, metric slices permit you to automatically create distinct revenue metrics for each product type. Metric slices are enabled for Fact Metrics. We offer two key ways to slice your metrics: Auto Slices and Custom Slices. ### Auto Slices These metric slices are defined in your [Fact Table](#fact-tables) and can be selectively enabled per each Fact Metric. When an auto slice is added to a Fact Metric, it is automatically added to all experiments that use the metric. This allows for a standardized deep-dive analyses across your experiments. To set up Auto Slices: 1. Navigate to your Fact Table that has the metrics and the columns by which you want to slice. Ensure the Fact Table SQL is returning the relevant column. If it isn't you can click "Edit SQL" on the Fact Table page, add your column (e.g. `product_type`) to the SELECT statement, and then press "Confirm Changes". Only **string** and **boolean** columns can be used. ![Configuring a column for metric slicing](https://mintcdn.com/growthbook-ea15456d/hKkmiLOwhH6E2xcD/static/images/metrics/metric-dim-config.png?fit=max\&auto=format\&n=hKkmiLOwhH6E2xcD\&q=85\&s=4d8f90e0685682c3a230cf327b793848) 2. With your column (`product_type`) in the Fact Table SQL, you now edit the column property in the "Columns" table. Click the edit icon next to your column and then check the "Enable Auto Slices" checkbox in the window that pops up. * For **string** columns, add the slices you want to split your metric by. We run a query to populate the top values for you, and you can update the values found in the data by clicking the "Refresh" button. Slice values here could be "apparel", "equipment", and more. An "other" slice will also be shown if any metric value is attributed to values outside of your selected slices. You can have these levels automatically refresh over time by enabling [auto-update slice levels](#auto-update-slice-levels). * For **boolean** columns, no other slice configuration is necessary—both "true" and "false" slices will be generated. A "null" slice will also be shown for nullable columns if any metric value is attributed to "null". ![Configuring slice levels](https://mintcdn.com/growthbook-ea15456d/hKkmiLOwhH6E2xcD/static/images/metrics/metric-dim-levels.png?fit=max\&auto=format\&n=hKkmiLOwhH6E2xcD\&q=85\&s=7b76861cb962eae985d6410a6482bed9) 3. Scroll down to the list of Fact Metrics, or navigate to a Fact Metric from this Fact Table. 4. Click "Edit" and select your slices you want to apply to that metric for all experiments. #### Auto-update slice levels You can enable automatic updates of auto slice levels based on top column values. This keeps your slice levels current with your data without manual intervention. **Fact Table level setting:** In the **Fact Table** modal, check the **Auto-update slice levels** checkbox. When enabled, a scheduled job periodically refreshes auto slice levels from **top values**, scanning the lookback window configured in your organization's **Data Source settings** (default 14 days). The job runs: * Every 7 days by default (configurable via `AUTO_SLICE_UPDATE_FREQUENCY_HOURS` environment variable for self-hosted users) * Automatically when the fact table changes * Manually when you click **Update** in the column modal ![Fact Table modal with auto-update slice levels checkbox](https://mintcdn.com/growthbook-ea15456d/hKkmiLOwhH6E2xcD/static/images/metrics/auto-slice-updates-fact-table.png?fit=max\&auto=format\&n=hKkmiLOwhH6E2xcD\&q=85\&s=bd88f2c214b00139ca364fd98452719c) #### Locking slice levels When auto-updates are enabled, you can **lock** specific slice levels in the column modal to ensure they are never removed by the auto-update job. This is useful for important segments or product types that you always want to track, even if they're not currently in the top values. In the column modal, each slice level has a **Lock** checkbox. Locked levels are always preserved when the top-values job runs, while unlocked levels may be replaced if they're no longer in the top values. ![Column modal showing slice levels with Lock checkboxes](https://mintcdn.com/growthbook-ea15456d/hKkmiLOwhH6E2xcD/static/images/metrics/auto-slice-levels-locking.png?fit=max\&auto=format\&n=hKkmiLOwhH6E2xcD\&q=85\&s=0980ddddbdd66abdf06199da8812ac7e) ### Custom Slices We also allow you to perform custom metric breakdowns across all of your experiment metrics for a single experiment. For example: "For this experiment, I want to see how treatment will affect apparel revenue and average order value in EU". Unlike auto slices, custom slices allow you to create filtering combinations from different columns (e.g., `product_type = 'apparel' and location = 'EU'`). You can add custom slices when setting up your experiment after selecting your metrics, or at any point afterwards. These slices will be run for all metrics in your experiment, but for that experiment only. Custom slices can be created using any non-user-identifier string or boolean column in your Fact Table. (Custom slicing is not limited to auto-slices columns.) ![Adding custom slices](https://mintcdn.com/growthbook-ea15456d/hKkmiLOwhH6E2xcD/static/images/metrics/custom-slices.png?fit=max\&auto=format\&n=hKkmiLOwhH6E2xcD\&q=85\&s=8a882808c977d214a82e4c9dab75cd1f) ### Viewing metric slices Using our revenue metric as an example, when you run an experiment you will now have the option to split revenue by product type (auto slice) and/or by "apparel and EU" (custom slice). If you click the arrow icon next to "revenue" on the experiment results table, you will see these slices expand under the base metric row (revenue), each slice materialized as its own sub-row. You also can use the Filter button at the top of the results to filter all metrics by slices, or click in to a row for the [Metric Drilldown](/app/experiment-results#metric-drilldown) to view slices with search, sorting, and time-series analysis capabilities. ![Metric slice results](https://mintcdn.com/growthbook-ea15456d/hKkmiLOwhH6E2xcD/static/images/metrics/metric-dim-results.png?fit=max\&auto=format\&n=hKkmiLOwhH6E2xcD\&q=85\&s=cace46ac3e6c82eb56bdd992d4a72d7a) > [!NOTE] > > **Dimensions versus Slices** > > Unit dimensions and experiment dimensions are attributes specific to an experimental unit (e.g. user). > In contrast, metric slices are an attribute specific to a metric. > For example, suppose that a user named Romain lives in France, and he buys products from both France and Germany. > In a dimension analysis (i.e. a unit dimension analysis), all of Romain's spend will be attributed to France. > > In a metric slice analysis, only the French products purchased will be attributed to French revenue, and only the German products purchased will be attributed to German revenue. > So one user will always be mapped to a single user dimension, but can contribute to multiple metric slices. > > All users (units) contribute to a metric slice. This provides the most accurate inference, as trying to filter down the denominator can cause bias issues if you allow users to switch between different dimension levels. ## Metric Analysis After creating a metric, you can perform some basic analysis on it. Depending on the metric type, we show you the average value over time, a histogram breakdown, and more. You can customize the date range and population that's included. For more advanced analysis, use our [Product Analytics](/app/product-analytics) feature. ## Metric Groups Enterprise **Metric Groups** is available on Enterprise plans. There are often groups of metrics you want to add together to an experiment. For example, 5 key revenue metrics that you add to anything touching the checkout flow. Define Metric Groups to accomplish this. Add multiple metrics, drag & drop to order them, and add them in bulk to experiments as Goals, Guardrails, or Secondary Metrics. ## Database Cost Optimization For large Fact Tables, it can be important to consider query costs, especially when using a data warehouse like BigQuery which charges based on data scanned. Read our dedicated guide on [Query Optimization](/app/query-optimization) for best practices on reducing query costs across GrowthBook. # Metric Examples and Use Cases Source: https://docs.growthbook.io/app/metrics/examples Examples of binomial, count, duration, and revenue metrics you can define on Fact Tables in GrowthBook. Fact Tables in GrowthBook are a powerful way to quickly build out a library of metrics. Below are some common types of tables in your data warehouse and how to map them to Fact Tables with example SQL and metric definitions. The **Add to GrowthBook** buttons will open GrowthBook and let you customize the metric before adding it to your account. GrowthBook Host: ## Raw Events Table The most common form of analytics data is a raw event stream. Every time a user does something, you append a new row to an events table in your warehouse. All types of events from all users are intermixed in this one table. Fact Tables in GrowthBook work really well with raw events. When defining your Fact Table in GrowthBook, there are 4 important types of columns you will want to select: 1. Timestamp - When the event occurred 2. Ids - Who triggered the event (user id, device id, etc.) 3. Event Type - What action was performed (page view, start checkout, add friend, etc.) 4. Context - Any helpful user or app context (geo-location, current url, account plan, etc.) When creating metrics, you will almost always want to filter on the Event Type. To make this easier, you can edit the column representing Event Type and check the “Prompt metrics to filter on this column” box (after creating your Fact Table). This helps GrowthBook better streamline the UI when creating metrics. ![Event Type Filter](https://mintcdn.com/growthbook-ea15456d/hKkmiLOwhH6E2xcD/static/images/metrics/inline-filter.png?fit=max\&auto=format\&n=hKkmiLOwhH6E2xcD\&q=85\&s=88cda3b588595dc8b04eb560b3ccaa9d) ### Example SQL: Google Analytics 4 (GA4) Events When you configure Google Analytics 4 to sync events into BigQuery, they get written to a raw events table (actually multiple tables partitioned by day, but that’s more of an implementation detail) Here’s example SQL for a fact table. Comments added to highlight the 5 column types above ```sql theme={null} SELECT -- Timestamp TIMESTAMP_MICROS(event_timestamp) as timestamp, -- Ids user_id, user_pseudo_id as anonymous_id, -- Event type event_name, -- Context event_value, -- e.g. the page view path geo.country, device.category as device_category FROM `events_*` ``` For faster and cheaper queries, we can take advantage of the date partitioning table scheme. GrowthBook lets you use date template variables that are evaluated right before executing the SQL. By doing this, we can restrict our query to just the relevant dates for the experiment we are analyzing. Change the end of the query and add the following WHERE clause: ```sql theme={null} FROM `events_*` WHERE -- Daily tables ((_TABLE_SUFFIX BETWEEN '{{date startDateISO "yyyyMMdd"}}' AND '{{date endDateISO "yyyyMMdd"}}') -- Realtime streaming intraday table OR (_TABLE_SUFFIX BETWEEN 'intraday_{{date startDateISO "yyyyMMdd"}}' AND 'intraday_{{date endDateISO "yyyyMMdd"}}')) ``` ### Example Metrics #### Percent of users who submit a form 1. Select `Proportion` as the metric type 2. Choose `form_submit` as the event type Add to GrowthBook #### Percent of users who visit the checkout page 1. Select `Proportion` as the metric type 2. Choose `page_view` as the event type 3. Add a “Row Filter” and select `page_path = '/checkout'` Add to GrowthBook #### Page Views per User 1. Select `Mean` as the metric type 2. Choose `page_view` as the event type 3. Select `Count of Rows` as the value Add to GrowthBook #### Unique Page Views per User 1. Select `Mean` as the metric type 2. Choose `page_view` as the event type 3. Select `event_value` as the column 4. Select `COUNT DISTINCT` as the aggregation Add to GrowthBook #### Active Days per User 1. Select `Mean` as the metric type 2. Choose `Distinct Dates` as the column Add to GrowthBook #### Checkout completion rate 1. Select `Ratio` as the metric type 2. For the **numerator** (confirmation) 1. Choose `page_view` as the event type 2. Add a Row Filter for `page_path = '/confirmation'` 3. Select `Unique Users` as the value 3. For the **denominator** (checkout) 1. Choose `page_view` as the event\_type 2. Add a Row Filter for `page_path = '/checkout'` 3. Select `Unique Users` as the value Add to GrowthBook ## Modeled Tables Another common way to store data in a warehouse is with modelling. This usually involves a tool like `dbt` that transforms raw event logs into multiple well-structured tables. There are also some event tracking tools like Segment which do this automatically. For our purposes, we are only considering modeled tables that do not include a GROUP BY in the data pipeline - in other words, the rows are still at the raw event-level. Check out the Pre-aggregated Tables section below if this is not the case. In GrowthBook, we recommend creating a separate Fact Table for each one of your modeled tables. At the very least, you will need a Timestamp and IDs of who performed the action. Any additional columns you add are optional and can be used to build out metrics. ### Example SQL: E-Commerce Orders Table ```sql theme={null} SELECT -- Timestamp received_at as timestamp, -- IDs user_id, -- Other custom columns num_items, grand_total, shipping_type FROM orders ``` Because this is a modeled table just for orders, we can select event-specific columns like `num_items`, `grand_total`, and `shipping_type`. These columns will help us build out a rich set of metrics. This is one of the big benefits over using a raw events table directly where the columns you select need to be more generic and work across a wide range of different events. Similar to raw event tables, you can optionally add a WHERE clause on dates for faster/cheaper queries: ```sql theme={null} FROM orders WHERE timestamp BETWEEN '{{startDate}}' AND '{{endDate}}' ``` ### Example Metrics #### Users who Purchased 1. Select `Proportion` as the metric type and that’s it! Add to GrowthBook #### Users who Purchased Multiple Times 1. Select `Proportion` as the metric type 2. Add a “User Filter” based on `Count of Rows` 3. Enter `>= 2` as the User Filter value Add to GrowthBook #### Orders per User 1. Select `Mean` as the metric type 2. Select `Count of Rows` as the metric value Add to GrowthBook #### Revenue per User 1. Select `Mean` as the metric type 2. Select `SUM(grand_total)` as the metric value Add to GrowthBook #### Average Items per Order 1. Select `Ratio` as the metric type 2. For the numerator, select `SUM(num_items)` 3. For the denominator, select `Count of Rows` Add to GrowthBook #### Users Without Orders 1. Select `Proportion` as the metric type 2. Add a “User Filter” for `Count of Rows = 0` 3. Under Advanced Settings -> Display Settings, change the goal to `Decrease the Metric Value` Add to GrowthBook #### Percent of Orders with Free Shipping 1. Select `Ratio` as the metric type 2. For the numerator, add a row filter for `shipping_type = 'free'` 3. Select `Count of Rows` for both the numerator and denominator Add to GrowthBook ## Pre-Aggregated Tables Pre-aggregated tables include a GROUP BY in the data pipeline to compress raw event-level data down to fewer rows. This is usually done when querying raw data directly is prohibitively expensive. GrowthBook supports pre-aggregated tables as long as they satisfy two requirements: * Must be grouped by both user and date * Pre-aggregated columns can only be basic sums or counts. No averages, percentiles, count distinct, or complex derived formulas that break statistical assumptions. Using pre-aggregated tables for experimentation comes with additional complexities and downsides, so we highly recommend sticking with event-level data whenever possible. ### Example SQL: Social Media Interactions (User/Date Rollup) If you have a social media app, the number of raw interactions might be huge. A user/date rollup table can drastically reduce the size of the data set. Here is an example Fact Table SQL definition in GrowthBook that references a user/date rollup table. ```sql theme={null} SELECT date as timestamp, user_id, -- Aggregated columns (sums/counts only) interactions, likes, comments FROM user_date_rollup ``` Pre-aggregated tables have a problem. The `timestamp` column isn’t very accurate. All events for a day are recorded as happening at the beginning of the day (usually at midnight). We only consider Fact Table rows with a timestamp after a user first enters an experiment. That basically means a user’s first day of data will be excluded from the analysis. This is a big deal because those first few hours after entering a test are often the most important. We can fix this by incrementing all timestamps by 1 day in our Fact Table definition (specific SQL varies by db engine): ```sql theme={null} SELECT DATE_ADD(date, INTERVAL '1 day') as timestamp, user_id ... ``` This introduces a new problem however - we will now potentially include actions from before a user saw an experiment, which can introduce noise to the results. In our experience, this is usually the lesser of two evils and thus, this is the approach we recommend. Of course, you can solve both of these problems by not pre-aggregating in the first place, but we understand sometimes that’s just not an option. ### Example Metrics #### Percent of Users with High Engagement 1. Select `Proportion` as the metric type 2. Add a “User Filter” and select `SUM(interactions)` as the value to filter on 3. Add some threshold as the User Filter value. Something like `> 100` Add to GrowthBook #### Interactions per User 1. Select `Mean` as the metric type 2. Select `SUM(interactions)` as the value Add to GrowthBook #### P90 Likes per User 1. Select `Quantile` as the metric type 2. Check the "Group by user" box 3. Select `SUM(likes)` as the per-user value 4. Select `P90` as the quantile level Add to GrowthBook #### Comment-to-Like Ratio 1. Select `Ratio` as the metric type 2. Select `SUM(comments)` as the numerator value 3. Select `SUM(likes)` as the denominator value Add to GrowthBook # Funnel Metrics Source: https://docs.growthbook.io/app/metrics/funnel Measure the share of experiment units who complete an ordered series of steps, and see where they drop off, with funnel fact metrics in GrowthBook. Pro **Funnel Metrics** is available on Pro and Enterprise plans. Funnel Metrics measure the share of exposed units who complete a series of steps in order. One metric gives you both the end-to-end completion rate and a per-step breakdown of where units drop off, so a single row in your experiment results expands into the whole journey. A funnel metric is a [Fact Table](/app/metrics) metric like any other. You define an ordered list of steps and GrowthBook turns each step into its own conversion rate. > [!NOTE] > > **Two things named "funnel"** > > This page covers the **funnel metric type** built on Fact Tables. It is unrelated to the [legacy denominator behavior](/app/metrics/legacy#denominator-ratio-funnel-metrics), where a binomial denominator metric acted as a filter on the numerator. Those legacy metrics should be replaced by these funnel metrics, although there are some slight differences in behavior. > > The [Funnel explorer](/app/product-analytics/explorer#funnels) in Product Analytics answers similar questions outside of an experiment. Funnel metrics are for experiment analysis; the explorer is for ad-hoc exploration. ## Creating a funnel metric 1. Go to **Metrics** and click **Add Metric**. 2. Pick your **Data Source**, then choose **Funnel** as the metric type. 3. Select the **Fact Table** the first step's events come from. 4. Name the first step and add a row filter that defines it, such as `event_name = view_product`. 5. Click **Add a step** for each additional step, in the order units should complete them. New steps start on the previous step's Fact Table and can be repointed to any other Fact Table in the same Data Source, although funnel metrics with steps on different Fact Tables will take longer to compute. 6. Set conversion windows and mark optional steps as needed, then save. A funnel needs at least 2 steps and supports up to 20. :::info Steps can span Fact Tables Each step selects its own Fact Table, so a funnel can move between tables — say a `view_product` event in an events table followed by a purchase in an orders table. Every step's table must belong to the metric's Data Source, and changing a step's Fact Table clears that step's row filters. In experiments, every step's Fact Table must share an identifier type with the experiment's assignment table (or reach it through a Join Table); otherwise the metric is excluded from analysis. ::: ### Step settings Each step has the following settings: * **Fact Table**: The table this step's events come from. New steps default to the previous step's table. * **Step name**: The label shown in the results table, the funnel chart, and CSV exports. Defaults to `Step N`. * **Row filters**: What counts as completing this step, such as `event_name = add_to_cart`. These work the same as [Row Filters](/app/metrics#row-filters) on any other metric, including saved filters and custom SQL expressions. * **Optional step**: Units who skip this step can still complete later steps. See [Optional steps](#optional-steps). * **Conversion window**: How long the unit has to reach this step. Enter a number and a unit of `minutes`, `hours`, `days`, or `weeks`. ### Metric-level settings Funnel metrics support [Metric Windows](/app/metrics#metric-windows), a [metric delay](/app/metrics#metric-delay), a target MDE, and [Bayesian priors](/app/metrics#bayesian-priors). The metric window bounds which events are eligible at all; step conversion windows then apply inside that envelope. [Metric Slices](/app/metrics#metric-slices) are not supported for funnel metrics. ## How steps are resolved Funnels are sequential: steps must happen in order, and a unit can complete a later step without having completed an optional one along the way. For each step, GrowthBook takes the **earliest** qualifying event that falls inside that step's window. The window is anchored on the nearest prior **required** step. The first step is anchored on experiment exposure, and so is any step whose prior steps are all optional. Take a three-step funnel where a unit is exposed at 10:00: | Step | Conversion window | Anchored on | Counts if the event happens | | - | - | - | - | | 1. Viewed product | 1 hour | Exposure at 10:00 | Between 10:00 and 11:00. Say it lands at 10:05 | | 2. Added to cart | 2 hours | Step 1 at 10:05 | Between 10:05 and 12:05. Say it lands at 11:30 | | 3. Purchased | 1 day | Step 2 at 11:30 | Between 11:30 and 11:30 the next day | A step with no conversion window has no upper bound. It counts as long as it happens after the previous non-optional step and inside the metric window. ### Optional steps An optional step is measured like any other step: you still get its conversion rate, its lift, and its own row in the results. What changes is that it does not anchor or filter the steps after it. In the example above, if **Added to cart** were optional and the unit never added to cart, **Purchased** would be measured within 1 day of **Viewed product** instead. Units who did add to cart are still anchored on their view, not their add to cart. Use optional steps for parts of a journey you want to measure but that are not required to continue. > [!NOTE] > > **Partial data** > > Metric level conversion windows are a hard cap on the completion of all steps. Data that lands after a conversion window has closed are not considered. If the experiment has **Skip Partial Data** enabled, where it controls which recent exposures are excluded as not having had time to convert, time to convert is measured as the sum of each steps conversion windows, capped by the metric window. ## Every step shares one denominator This is the most important thing to know about funnel metrics: **each step is measured against all exposed units, not just the units who completed the previous step.** If 1,000 units are exposed to a variation and 400 view a product, 120 add to cart, and 60 purchase, the three steps report 40%, 12%, and 6%. They do not report 40%, 30%, and 50%. That choice is critical for deriving the correct interpretation from your results. Using the conversion total from the previous step as the denominator for your next step will mislead most experimenters, because it carries forward the indirect effect of the last step into the current step's analysis. **Example: why step-to-step conversion rates can be misleading** Suppose you run an experiment on a two-step funnel: Add to Cart -> Purchase, with 1,000 users in each variation. Imagine we know the true effect: the treatment brings in many new, lower-intent cart-adders, but checkout rates are unchanged by this experiment (low-intent users still checkout at the same rates as before and high-intent users still checkout at the same rates as before. | Metric | Control | Treatment | | - | - | - | | Users assigned | 1,000 | 1,000 | | Added to cart | 200 (20%) | 400 (40%) | | Purchased | 100 | 140 | | Cart → Purchase rate (conditional) | 50% | 35% | | Assignment → Purchase rate (unconditional) | 10% | 14% | The step-to-step, conditional conversion rate (Cart → Purchase) fell from 50% to 35% so it looks like the treatment hurt checkout. But nothing about checkout changed. The control arm's carts had 200 high-intent shoppers; the treatment arm added 200 casual browsers who convert at \~20%, dragging the average down. Reading the 50% -> 35% drop as "checkout got worse" conflates the treatment's effect on who reaches the cart with its effect on checkout itself. The last row is what GrowthBook would represent as the second step conversion rate and it tells the true story in this case: purchases per assigned user rose from 10% to 14%. That comparison is safe because randomization guarantees the two groups of 1,000 are comparable, comparing the groups of cart-adders are not. This is an example of the well known "post-treatment bias" problem, and is akin to what happens when sample ratio mismatch is a sign of assignment bias. Step-to-step conversion rates are still available in the [funnel chart](/app/experiment-results#funnel-metric-results) in the metric drilldown, but we do not currently provide them as a top-level metric with the confidence intervals and statistics used for decision making. Those are reserved for the safer, unconditional conversion rates. The metric's own value is the share of exposed units who reach the **last** step, which is your end-to-end completion rate. ## Where funnel metrics can be used Funnel metrics work as **goal**, **secondary**, and **guardrail** metrics on experiments and reports, including with dimensions, date results, and [Experiment Dashboards](/app/experiment-dashboards). The following are not supported yet: | Feature | Behavior | | - | - | | Activation metrics | Funnel metrics are hidden from the activation metric dropdown. Use a proportion metric instead. | | [Metric Slices](/app/metrics#metric-slices) | Not supported. The drilldown's Slices tab is replaced by a Funnel tab. | | [CUPED and regression adjustment](/statistics/cuped) | Always off for a funnel and its steps, because the funnel query does not emit covariate columns. CUPED support for funnel metrics is coming soon! | | Standalone metric analysis | The metric page cannot chart a funnel on its own. It is analyzed in the context of an experiment. | | [Product Analytics Explorer](/app/product-analytics/explorer) | Funnel metrics cannot be charted in the Explorer or in a metric explorer dashboard block, and this is coming soon! In the meantime, use the [Funnel explorer](/app/product-analytics/explorer#funnels) for ad-hoc funnel analysis. | | Incremental Refresh | Funnel metrics are not supported for Incremental Pipeline experiments yet, but support is coming soon! For now, remove them from the experiment, or run the experiment without incremental refresh. | | [Bandit experiments](/bandits/overview) | Funnel metrics cannot be used as the decision metric or as secondary or guardrail metrics on Bandits. | | Capping and quantile settings | Not supported. | ### Supported data sources Funnel metrics run on Postgres, ClickHouse (including the [Managed Warehouse](/app/managed-warehouse)), BigQuery, Snowflake, Athena, Presto, Databricks, and Redshift. > [!WARNING] > > MySQL, MSSQL, and Vertica cannot run funnel queries. Adding a funnel metric to an experiment on one of these data sources fails the query with an unsupported data source error naming the funnel metrics involved. Mixpanel and Google Analytics do not support Fact Tables at all. ## Funnel metrics in experiment results In the results table, a funnel metric is one row you can expand into a row per step. Each step row shows that step's conversion rate, lift, and significance against the shared denominator, and steps always stay in funnel order no matter how you sort. Clicking the metric opens the drilldown with a **Funnel** tab that charts each variation's funnel side by side. See [Funnel Metric Results](/app/experiment-results#funnel-metric-results) for the full walkthrough. ## Managing funnel metrics with the API Create a funnel metric by setting `metricType` to `funnel` and passing `funnelSettings`. Omit `numerator` and `denominator`; they are not allowed on funnel metrics. The metric inherits its data source from the first step's Fact Table, and steps may reference different Fact Tables as long as they all belong to that data source. ```bash theme={null} curl -X POST 'https://api.growthbook.io/api/v1/fact-metrics' \ -H 'Authorization: Bearer YOUR_API_KEY' \ -H 'Content-Type: application/json' \ -d '{ "name": "Checkout Funnel", "owner": "you@example.com", "metricType": "funnel", "funnelSettings": { "steps": [ { "name": "Viewed product", "factTableId": "ft_events", "rowFilters": [ { "column": "event_name", "operator": "=", "values": ["view_product"] } ], "optional": false, "conversionWindow": { "unit": "hours", "value": 1 } }, { "name": "Added to cart", "factTableId": "ft_events", "rowFilters": [ { "column": "event_name", "operator": "=", "values": ["add_to_cart"] } ], "optional": true, "conversionWindow": { "unit": "hours", "value": 2 } }, { "name": "Purchased", "factTableId": "ft_events", "rowFilters": [ { "column": "event_name", "operator": "=", "values": ["purchase"] } ], "optional": false, "conversionWindow": { "unit": "days", "value": 1 } } ], "ordering": "sequential", "concurrencyWindowSeconds": 0 } }' ``` Field reference for `funnelSettings`: * **steps**: Required. An ordered array of 2 to 20 steps. Each step needs `name`, `factTableId`, `rowFilters`, and `optional`; `conversionWindow` is optional and may be `null`. Steps may use different `factTableId`s within the metric's data source. * **ordering**: Optional, defaults to `sequential`. Only `sequential` is accepted today. * **concurrencyWindowSeconds**: Optional, defaults to `0`. Tolerance in seconds for events that arrive slightly out of order, applied to the lower bound of each step's window. `GET` responses for funnel metrics include `funnelSettings` and omit `numerator`. Listing fact metrics filtered by `factTableId` returns funnel metrics whose steps use that Fact Table. Per-step results are exposed in the [bulk experiment results API](/app/experiment-bulk-results-api) as derived metric ids of the form `?step=`, zero-indexed, named `Funnel name: Step name`. These ids are read-only. They cannot be created, queried, or added to an experiment on their own. ## What's next * [Understanding Experiment Results](/app/experiment-results#funnel-metric-results) for reading funnel results in an experiment. * [Metrics and Fact Tables](/app/metrics) for row filters, metric windows, and the other metric types. * [Funnel explorer](/app/product-analytics/explorer#funnels) for ad-hoc funnel analysis outside of an experiment. # Legacy Metrics Source: https://docs.growthbook.io/app/metrics/legacy Define legacy SQL metrics for experiment analysis if you are not yet using Fact Tables. Prefer Fact Tables for new metrics. Metrics are what your experiments are trying to improve (or at least not hurt). GrowthBook has a very flexible and powerful way to define metrics. > [!WARNING] > > **Legacy Metrics** > > This page describes Legacy Metrics. We highly recommend using **Fact Tables** to define metrics instead. [Check out the docs](/app/metrics) ## Conversion Types Metrics can have different units and statistical distributions. Below are the ones GrowthBook supports: | Conversion Type | Description | Example | Default aggregation (SQL) | | - | - | - | - | | binomial | A simple yes/no conversion | Created Account | 1/0 per unit | | count | Sums conversion values per user | Pages per Visit | `SUM(value)` per unit | | duration | How much time something takes | Time on Site | `SUM(value)` per unit | | revenue | The revenue gained/lost | Revenue per User | `SUM(value)` per unit | For experiment analysis, each of these metric types uses some aggregation (often defaulting to `SUM`) per user and then takes an average with respect to the total number of users. In the case of SQL metrics, the only meaningful difference between count, duration, and revenue is how we render the units in the metric and experiment results pages. > [!NOTE] > > **USD by default** > > Revenue Metrics are displayed in USD by default. You can change your display currency under **Settings** → **General** → **Metric Settings** ## Query settings For metrics to work, you need to tell GrowthBook how to query the data from your data source. There are a few ways to do this depending on your data source. ### 1. SQL (recommended) If your data source supports SQL, this is the preferred way to define metrics. You can use joins, subselects, or anything else supported by your SQL dialect. Your SELECT statement should return one row per "conversion event". This may be a page view, a purchase, a session, or something else. The end result should look like this: | user\_id | timestamp | value | | - | - | - | | 123 | 2021-08-23 12:45:14 | 10 | | 456 | 2021-08-23 12:45:15 | 5.25 | Metrics can support one or more types of identifiers. The above example assumes the metric only supports a single id type called `user_id`, but you would add additional columns if you need to support other ones. #### Non-binomial metrics For count, revenue, and duration metrics metric types, the value represents the count, duration, or revenue from that single conversion event. In the case of multiple rows for a single user, the values will be summed together or we will use a custom aggregation that you can specify. Therefore a `count` metric can really be any arbitrary metric whose `value` you want to sum at the user level before taking an average per variation. If you use Segment to populate your data warehouse, the SQL for a `Revenue per User` metric might look like this: ```sql theme={null} SELECT -- Assuming you support 2 identifier types - 'user_id' and 'anonymous_id' user_id as user_id, anon_id as anonymous_id, received_at as timestamp, grand_total as value FROM purchases ``` If you wanted to count the number of conversion rows per user, you can simply set `1 as value` in your SQL query and then the default SUM aggregation will count the number of rows per user. #### Binomial metrics Binomial metrics don't need a `value` column (the existence of a row means the user converted). You would only need to return the following columns, representing users and when they "converted" on this binomial metric: | user\_id | timestamp | | - | - | | 789 | 2022-08-23 12:45:14 | | 111 | 2022-08-23 12:45:15 | When we go to conduct experiment analysis, any `user_id` that has a conversion in the appropriate time window will be counted as a `1`, while all other users will be counted as a `0`. Then we can compute the proportion of users in an experiment variation who converted. #### SQL Templates We use \{\{[Handlebars](https://handlebarsjs.com/guide/#language-features)}} to compile the sql into what is actually called to your database. This allows you to create template metrics that can be copied and reused by changing the variable values associate with the metrics. You can use the following user configurable variables within SQL templates: * **eventName** - The event name associated with this metric. This can then be referenced in your sql template as `{{eventName}}`. Depending upon how your data is structured you can then incorporate it as part of the table name, if each event has its own table, or as part of a where clause limiting the rows returned to where a certain column equals the eventName. * **valueColumn** - The column in your datawarehouse table with the metric data. This can then be referenced in your sql template as `{{valueColumn}}`. For example you might have `{{valueColumn}} as value` to extract out the value from the table. Read more about [SQL Templates](/app/sql-templates) and all the different variables that are available. #### Denominator (Ratio / Funnel Metrics) By default, metrics are evaluated against all users in an experiment: `(# users who converted) / (# users in experiment)` You can instead choose another metric to use as the denominator. ##### Funnel Metrics When the denominator is a simple binomial (conversion) metric, then it acts just like an "activation metric" in an experiment. It filters the users who are included in the analysis to those who first convert on this denominator metric. For example, if you want to look at what percent of users checkout after viewing a cart, it can be described as `% checkout / % viewed cart`. This requires creating two metrics: 1. `Viewed Cart` - selects all users who viewed a cart 2. `Viewed Cart -> Checkout` - selects all users who checked out and picks `Viewed Cart` as the denominator. > [!TIP] > > Fact Tables have a dedicated [Funnel Metric](/app/metrics/funnel) type for this. One funnel metric defines both steps, reports a conversion rate for each of them, and does not require chaining two metrics together. ##### Ratio Metrics When the denominator is a count metric, then things are a little different. Instead of acting like a filter, we calculate both metrics and treat the value as a ratio. * The mean is `sum(metric) / sum(denominator)` * The standard deviation is calculated using the **Delta method** For example, if you want to look at the Average Order Value (AOV), what you're really looking for is `total revenue / number of orders`. This also requires creating two metrics: 1. `Orders per User` - selects the count of orders for each user 2. `AOV` - selects total revenue per user and picks `Orders per User` as the denominator. ##### Quantile Metrics [Quantile Metrics](/statistics/quantile) are available only as Fact Tables. See [Quantile Metrics in Fact Tables](/app/metrics#quantile-metrics). ### 2. JavaScript (Mixpanel only) We query Mixpanel data sources using their proprietary JQL language based on JavaScript. This allows for extreme flexibility when defining metrics. All metrics at minimum need to specify an **Event Name** which must exactly match what is used in Mixpanel. You can use `OR` to match against multiple events. For example `viewed_cart OR purchased` You can optionally add **Conditions** which filters the events further based on properties. For example, if Event Name is `Page view`, you can add a condition `path = "/blog"`. Count, duration, and revenue metrics have two additional steps. We first extract all event values for a user into an array and then reduce that array down to a single number, which is the final metric value for the user. #### Conditions Conditions in Mixpanel are very powerful. They consist of a **Property**, an **Operator**, and a **Value**. Multiple conditions are joined with an AND. The **Property** can either be the name of an event property or a JavaScript expression. Some examples: * `amount`, equivalent to `event.properties.amount` * `event.time` * `event.properties.city + ", " + event.properties.country` The **Operator** is one of the following: * equals * does not equal * is greater than * is greater than or equal to * is less than * is less than or equal to * matches the regex * does not match the regex * custom javascript The `custom javascript` operator is special. The **Value** is a JavaScript expression that evaluates to either `true` or `false` (access the property value with the `value` variable). It lets you do arbitrarily complex filtering. For example: ```js theme={null} value < 5 || value > 10; ``` For all other operators, the condition should read like an English sentence. For example: ```js theme={null} // property operator value country equals US // Will render as event.properties.country == "US" ``` #### Event Value For count, revenue, and duration metrics, we need to know what the "value" of the event is. The **Event Value** is a JavaScript expression to extract a value from a raw Mixpanel event. If you are just extracting a single property as-is, you can just enter the property name as a shortcut. Otherwise, you can reference the `event` variable in your expression. Here are some example Event Value expressions: * `grand_total`, equivalent to `event.properties.grand_total` * `1` (hard-code the value to a specific number) * `(event.properties.endTime - event.properties.startTime) / (60 * 60)` (difference in hours between two unix timestamps) * `new Date(event.time).toISOString().substr(0, 10)` (event timestamp in YYYY-MM-DD format) For count metrics, you can leave Event Value blank and it will default to hard-coding the value to `1`, which is perfect for when you just want to count the number of events and don't care about specific properties. #### User Value Aggregation For count, revenue, and duration metrics, we need to know how to aggregate the event values together, in case a single user has multiple matching events. The **User Value Aggregation** is another JavaScript expression that reduces an array of Event Values to a single number (or null if the user did not convert). Reference the variable `values` in your expression. There are a few built-in helper functions: * `count(values)` * `countDistinct(values)` * `sum(values)` * `min(values)` * `max(values)` * `avg(values)` * `median(values)` * `percentile(values, p)` (p is a number between `0` and `100`) You can use your own custom expression too if you want. For example, this is the equivalent of `sum(values)`: ```js theme={null} values.reduce((sum, n) => sum + n, 0); ``` If the aggregation is left blank, we do `sum(values)` by default. ### 3. SQL Query Builder (legacy) The query builder prompts you for things such as table/column names and constructs a SQL query behind the scenes. We only recommend this for extremely simple metrics. Inputting raw SQL is far more flexible. ## Behavior The behavior tab lets you tweak how the metric is used in experiments. Depending on the metric type and datasource you chose, some or all of the following will be available: ### What is the Goal? For the vast majority of metrics, the goal is to increase the value. But for some metrics like "Bounce Rate" and "Page Load Time", lower is actually better. Setting this to "decrease" basically inverts the "Chance to Win" value in experiment results so that "beating" the control means decreasing the value. This will also reverse the red and green coloring on graphs. ### Capped Value Large outliers can have an outsized effect on experiment results. For example, if your normal revenue per user \$40 and someone happens to make a \$5000 order, whatever variation that person is in will be much more likely "win" any experiment because that one order is an outlier. Capping (also known as winsorization) works by ensuring that all aggregate unit (e.g. user) values are no more than some value. So in the above example, if the cap was \$100, the \$5000 purchase will still be counted, but the aggregated value for that user will be capped at \$100 and will have a much smaller effect on the results. It will still give a boost to whatever variation the person is in, but it won't completely dominate all of the other orders and is unlikely to make a winner just on its own. Another way to think about this is that you are slightly biasing your results by truncating large values, but you are reducing variance to prevent the outsized effect of outliers. There are two ways to cap metric values in GrowthBook: **1. Absolute capping** - if set above zero, all aggregated user values will be capped at exactly this value. For example, if the cap is \$100 on total revenue per user, then after we sum all of a user's orders up, any user with an aggregate sum of greater than \$100 will be set to \$100. **2. Percentile capping** - when this is set to between 0 and 1, it uses that percentile to select a cap based on the data in your experiment so far. This cap is therefore specific to each experiment and specific to each analysis run in that experiment if new data has come in. It works like so: after we calculate the unit-level aggregate values for all units (e.g. users) during an experiment analysis, we find the specified percentile of these unit-level aggregates and then cap these aggregated values at this percentile. Using the above example, if you were to specify percentile capping with a value of `0.95`, then we find the 95th percentile of total revenue per user (say this turns out to be \$135). We then cap those user-level aggregates at \$135. You can additionally choose to ignore zeros, which will compute the percentile without including any user aggregated zero values. This is useful if you have a lot of zero values and you don't want to have to fine tune the percentile to avoid setting the cap too low. Because the percentile cap depends on the data in your experiment, it can be different from experiment to experiment, or even analysis to analysis. To find out what value was actually used for capping you can do the following: on the Experiment Results tab, click the three dot menu in the top right and select "View Queries". Each percentile capped metric will have a column with the `main_cap_value` that was used to cap that metric and represents the computed percentile of unit-level aggregate values. ### Metric Windows When used in an experiment, we only consider rows of a Metric where the timestamp is greater than or equal to the first time the user was exposed to the experiment. In other words, if someone purchases something before seeing your experiment, it won't be included in the analysis. This behavior is ideal for the vast majority of metrics, but you can change it with the Metric Delay setting if desired (see below). There are three window settings one can use to configure the metric date window. Each of them defines the lower and upper date range of the metric to use for each user: * **None** (default) * Lower bound: user's first exposure plus the metric delay * Upper bound: experiment end date * **Conversion Window** * Lower bound: user's first exposure plus the metric delay * Upper bound: the lower bound + the length of the conversion window * **Lookback Window** * Lower bound: the experiment end date minus the lookback window OR the user's first exposure plus the metric delay, whichever is later * Upper bound: experiment end date Here's a graphical representation of these three window types for a hypothetical User 1: ![Metric Windows](https://mintcdn.com/growthbook-ea15456d/hKkmiLOwhH6E2xcD/static/images/metric-windows.png?fit=max\&auto=format\&n=hKkmiLOwhH6E2xcD\&q=85\&s=facb42800cfd4d1b962f83dd9f8d4df6) Here's a second example for a hypothetical User 2, who joins the experiment late. Notice that the conversion window can extend beyond the experiment end date. ![Metric Windows (User 2)](https://mintcdn.com/growthbook-ea15456d/hKkmiLOwhH6E2xcD/static/images/metric-windows-user-2.png?fit=max\&auto=format\&n=hKkmiLOwhH6E2xcD\&q=85\&s=aebb492bd81eb15e3fc3274c1310864e) Why might you choose one window over another? **None** - The simplest. Use all data available. This is useful for using as much data associated with users in your experiment and will combine any behavior that is right after experiment exposure as well as long run behavior within the experiment time frame. **Conversion window** - Conversion windows allow you to only look at events that are tied to the first exposure to an experiment. This can help if, for example, you are tracking purchases and you only want to measure the effect of an experiment in a checkout flow on purchases made soon after seeing that checkout flow. Using a conversion window can reduce the noise from user behavior not related to an experiment. However, if you set the window too short, you may not capture users that return a few days later and were influenced by the experiment. **Lookback window** - Lookback windows are good for capturing long run impacts of an experiment on regular behavior like user log ins or page views. They have two main advantages: (1) You can mitigate the novelty effect of an experiment; if you are testing a new recommendation algorithm, at first users may react a certain way to the experiment, but eventually they may adjust and so may their behavior. In these cases, you may just want to look at the last 14 days of an experiment. (2) Lookback windows help you focus on the long run effects of an experiment. Much of experimentation is about building a better product; by focusing on impact of an experiment after it has been live for a week or two, you may get a better picture of the long run impact of launching the experiment. Larger companies who measure long run logged in behavior want to ensure their experiments have lasting effects and often rely on lookback windows to make shipping decisions However, lookback windows may not be right if you are testing a feature on logged-out or anonymous users and are measuring simple purchase conversions or something similar. In these cases, you might end up with many logged-out users who, in the long run, simply have no metric data associated with them. ### Metric Delay Conversions within the first X hours of being put into an experiment are ignored (default = `0`). This is useful for metrics like "day 2 retention". In that case, if your underlying table reports whether a user is retained on any given day, you could set a metric delay to `24` hours. #### Negative metric delays The metric delay can also be negative to include some conversions **before** a user is put into an experiment. For example, a value of `-2` would mean conversions up to 2 hours before will be included. You might be wondering when this would ever be useful. Imagine the average person stays on your site for 60 seconds and your experiment can trigger at any time. If you just look at the average time spent after the experiment, the numbers will lose a lot of meaning. A value of `20 seconds` might be horrible if it happened to someone after only 5 seconds on your site since they are staying a lot less time than average. But, that same `20 seconds` might be great if it happened to someone after 55 seconds since their visit is a lot longer than usual. Over time, these things will average out and you can eventually see patterns, but you need an enormous amount of data to get to that point. If you set the metric delay to something negative, say `-0.5` (30 minutes), you can reduce the amount of data you need to see patterns. For example, you may see your average go from 60 seconds to 65 seconds. Keep in mind, these two things are answering slightly different questions. `How much longer do people stay after viewing the experiment?` vs `How much longer is an average session that includes the experiment?`. The first question is more direct and often a more strict test of your hypothesis, but it may not be worth the extra running time. ### Bayesian Priors Your organization can set default priors for Bayesian analyses that are used by all metrics. However, you can also set metric specific priors by opening the Edit Metric modal from the Metric page, clicking on Advanced Settings, and turning on the metric override. This will allow you to set a custom prior for that metric. Additionally, you can use experiment metric overrides to further customize these priors for each experiment. You can read more about Bayesian priors on [our statistical details page](/statistics/details). ## Auto Generate Metrics When using GrowthBook with certain event trackers, we may be able to generate metrics for you automatically by identifying the unique events tracked by your event tracker. This is currently only supported for a few event trackers (listed below), but we are working to expand this list. If you are using one of the supported event trackers and would like to see what metrics we can create for you, head to the `Metrics` page in GrowthBook and select the `Discover Metrics` button. > [!NOTE] > > **Auto Generate Metrics limitations** > > When querying your datasource to identify unique events, we're currently only looking at events in the last 7 days. ### Supported Event Trackers * Segment * Rudderstack * Google Analytics 4 (GA4) * Amplitude ## Examples Let's walk through some examples of creating binomial, count, and retention metrics with GrowthBook. For all of the metrics below, let's pretend we have some table called `events`, which has one row per event tracked to your warehouse. For each row we have the following columns: * `user_id` - the id of the user * `timestamp` - the time the event was counted * `event_name` - the name of the event, we'll focus on 'purchase' as a key event types * `value` - the total value of the event, in this case the total value of a purchase From this table, we can build many different metrics: | Name | Metric Type | SQL | Aggregation | Denominator | Metric Delay | Metric Window | | - | - | - | - | - | - | - | | Any Purchase | Binomial | SELECT user\_id, timestamp FROM events WHERE event\_name = 'purchase' | n/a | | 0 | | | Number of Purchases | Count | SELECT user\_id, timestamp, 1 as value FROM events WHERE event\_name = 'purchase' | default (SUM) | | 0 | | | Order Value | Revenue | SELECT user\_id, timestamp, value FROM events WHERE event\_name = 'purchase' | default (SUM) | | 0 | | | Average Order Value | Revenue (ratio) | SELECT user\_id, timestamp, value FROM events WHERE event\_name = 'purchase' | default (SUM) | Number of Purchases | 0 | | | 7-Day Retention | Binomial | SELECT user\_id, timestamp FROM events | n/a | | 24\*7=144 hours | | | Active User Last 14d | Binomial | SELECT user\_id, timestamp FROM events | n/a | | 0 | Lookback - 14 day | If you wanted to only count purchases and purchase values made in the 72 hours after a user's first exposure to an experiment, then you could set the metric window to a conversion window of 72 hours. If you wanted to just count any user who made a purchase any time after experiment exposure, then set the metric window to none. ## Migrating Legacy Metrics to Fact Tables We recommend using Fact Tables for all new metrics. Legacy metrics will continue to be supported, but they will not get any new features. If you would like to migrate existing legacy metrics to Fact Tables, there are a few differences to be aware of. ### Reusable Definitions Metric definitions have been split up into a few different reusable pieces. It's a couple more steps to create your first metric, but it should drastically simplify adding subsequent ones and building out your metric library. * The SQL and supported user identifiers are defined in the **Fact Table** * The display formatting (e.g. currency vs duration) is attached to a Fact Table **Column** * Any WHERE clauses for metrics are defined as **Filters** (e.g. `device_type = 'mobile'`) Once these pieces are in place, defining Metrics is now much easier. The form went from 13+ steps down to just 4 and no longer requires any specialized SQL or database knowledge. ### Metric SQL Before, SQL for a metric would select at most 1 numeric column and it had to be named `value`. With Fact Tables, you can have as many numeric columns as you want and there are no naming restrictions. This allows you to have 1 complex SQL definition be re-used across many related metrics. Also, Custom aggregations are no longer supported. Fact Tables only support a few pre-defined aggregations - `COUNT` and `SUM` (we may add more in the future). Using these pre-defined aggregations greatly simplifies the queries we run and enables advanced performance and cost optimizations. ### Metric Types Metric types have changed. * Binomial Metrics have been renamed to **Proportion Metrics** * Count, Duration, and Revenue are all now just **Mean Metrics** (the display formatting is controlled by the Fact Table columns now) * Instead of just adding a denominator to any metric, there is now a dedicated type for **Ratio Metrics**. ### Ratio Metrics There are a lot of changes to how Ratio Metrics are defined and how they behave. Ratio metrics are now self-contained. Previously, the denominator would just be a pointer to an entirely separate metric. This allowed some weird edge cases like the denominator itself being a ratio metric with its own denominator. With Fact Tables, you define both the numerator and denominator in one flow and these nested denominator edge cases are no longer supported. During analysis, Ratio metrics used to behave like a Funnel - the denominator had to happen first before the numerator. This was unintuitive for many people, so With Fact Tables, we changed them to act like true ratios - we calculate the numerator and denominator independently and then divide them. To measure an ordered sequence of events, use a [Funnel Metric](/app/metrics/funnel) instead. Lastly, ratio Fact Metrics have a single metric window and capping configuration. Legacy metrics control these separately for the numerator and denominator. Fact Metrics support both absolute and percentile capping, applied separately to each user's aggregated numerator and denominator. Absolute capping uses the same numeric bound for both components; percentile capping calculates a separate bound for each component using the shared percentile setting. # Customizable Pre-Launch Checklist Source: https://docs.growthbook.io/app/pre-launch-checklist Create and manage customizable pre-launch checklists so every experiment meets your team's requirements before it starts. Enterprise **Custom Launch Checklist** is available on Enterprise plans. Use GrowthBook's customizable pre-launch checklists to ensure all requirements are met before running an experiment. This guide explains how to create and manage these checklists. ![The prelaunch checklist menu with text explaining how to add a new task](https://mintcdn.com/growthbook-ea15456d/hKkmiLOwhH6E2xcD/static/images/prelaunch-checklist.png?fit=max\&auto=format\&n=hKkmiLOwhH6E2xcD\&q=85\&s=27551045dfeed8d7652b4ebde90bad4c) > [!TIP] > > **Why pre-launch checklists are important** > > Learn more about [why pre-launch checklists are important](https://blog.growthbook.io/custom-prelaunch-checklists/). ## How to Create a Pre-Launch Checklist Go to **Settings** → **General** and click **Edit Checklist**. From the menu, click **Add Your First Task**. ### Add a Pre-Defined Task GrowthBook offers 5 pre-defined, common tasks: 1. Add a descriptive hypothesis for this experiment 2. Upload a screenshot for each variation of the experiment 3. Add a description for this experiment 4. Add this experiment to a project 5. Add at least 1 tag to this experiment GrowthBook evaluates the status of these tasks automatically, checking off the task when the requirement is met. ### Add a Custom Task To define a custom task, type the item in the text box and click **Add Task**. Add an optional URL to link to resources or documentation. > [!TIP] > > **Reorder tasks by dragging and dropping them into the desired order.** Click **Save** to enable the checklist on all experiments. ### Project-Level Checklists You can also create a project-level checklist. This checklist will override the organization-level checklist for all experiments in the project. To create a project-level checklist, go to **Settings** → **Projects** and select the project you want to create a checklist for. Here you can scroll to the `Experiment Settings` section and click `Create Checklist`. If you change your mind, and you'd instead just like to use your organization's default checklist, you can delete the project-level checklist by clicking the `Delete Checklist` button. # Product Analytics Source: https://docs.growthbook.io/app/product-analytics Explore your data, build dashboards, and turn metrics into product insights without leaving GrowthBook. Product Analytics turns the data and metrics you've already configured in GrowthBook into a general-purpose analytics workspace. Visualize KPIs, explore raw events, run ad-hoc SQL, and share curated views with your team — without launching an experiment. Product Analytics is made up of two surfaces: * **[Explorer](/app/product-analytics/explorer)** — Build one-off charts from your metrics, fact tables, or raw warehouse tables. Ask questions in natural language, or configure explorations manually. * **[Dashboards](/app/product-analytics/dashboards)** — Combine multiple explorations, SQL queries, and markdown into a single shareable view that auto-refreshes on a schedule. Both surfaces read from the same [data sources](/warehouses), [fact tables, and metrics](/app/metrics) you already use for experimentation, so there's no separate event pipeline or instrumentation to set up. ## Plan Availability | Capability | Plan | | :- | :- | | Explorer (Metrics, Fact Table, Data Source) | All plans | | AI Chat in the Explorer | Pro+ (requires the AI Suggestions commercial feature) | | Custom SQL Reports (saved queries + visualizations) | Pro+ | | Product Analytics Dashboards (private) | Pro+ | | Sharing dashboards across the organization | Enterprise | ## Getting Started 1. Connect a [data source](/warehouses) so GrowthBook can run queries against your warehouse. If you don't have a warehouse yet, you can use a [Managed Warehouse](/app/managed-warehouse) to get started without provisioning your own. 2. Define [fact tables and metrics](/app/metrics) — these power the Metric and Fact Table explorers, and they're what dashboard blocks visualize. 3. Open **Product Analytics** in the left sidebar to start exploring. The first time you point an explorer at a data source, GrowthBook builds an *information schema* — an index of databases, schemas, tables, and columns — so the Fact Table and Data Source explorers can show you what's available. This is a one-time, per-data-source step that runs in the background. ## What's Next * **[Build your first chart](/app/product-analytics/explorer)** in the Explorer. * **[Assemble a dashboard](/app/product-analytics/dashboards)** that combines multiple charts, SQL queries, and context. * **[Connect Custom SQL Reports](/app/product-analytics/explorer#custom-sql-reports)** for analyses that don't fit a structured explorer. # Dashboards Source: https://docs.growthbook.io/app/product-analytics/dashboards Combine explorations, custom SQL, and markdown into curated, auto-refreshing dashboards. Share insights with your team without leaving GrowthBook. Pro **Product Analytics Dashboards** is available on Pro and Enterprise plans. Pro organizations can create unlimited private dashboards. Enterprise organizations can also share dashboards with the rest of the organization. Product Analytics Dashboards are general-purpose dashboards built from the same explorations you create in the [Explorer](/app/product-analytics/explorer). Use them to monitor KPIs, document findings for stakeholders, or give a team a single place to track the metrics they care about. > [!NOTE] > > Product Analytics Dashboards are separate from [Experiment Dashboards](/app/experiment-dashboards), which are scoped to a single experiment. The block types and configuration overlap, but the two surfaces are managed independently. ## Creating a Dashboard 1. Navigate to **Product Analytics → Dashboards**. 2. Click **Create Dashboard** in the top right. 3. Configure the dashboard's settings (see below) and click **Create**. Once created, you'll be dropped into the dashboard editor where you can start adding blocks. ### Dashboard Settings When creating or editing a dashboard, you can configure: * **Name** — A descriptive title for the dashboard. * **Projects** — Limit the dashboard to one or more [projects](/account/user-permissions#project-specific-permissions). Leave empty to make it available across all projects. * **Auto-update** — When enabled, GrowthBook refreshes the dashboard's data on a schedule (see [Update Schedules](#update-schedules)). * **View access** — Who can open the dashboard: * **Organization members** — Anyone in your organization can view it. * **Only me** / **Owner only** — Only the owner (and admins) can view it. * **Edit access** — Who can modify the dashboard: * **Any organization members with editing permission** — Other users with dashboard edit permission can change the contents. * **Only me** / **Owner only** — Only the owner (and admins) can edit it. When **View access** is set to private, **Edit access** is forced to private as well — you can't have a dashboard nobody can see but other people can edit. > [!NOTE] > > Sharing dashboards across the organization requires the **Share Product Analytics Dashboards** commercial feature, which is included on Enterprise plans. On Pro plans, dashboards are limited to private (owner-only) view and edit access. ### Update Schedules When **Auto-update** is enabled, you can choose how often the dashboard refreshes: * **Stale-based** — Refresh whenever the data is older than *N* hours. Defaults to 6 hours; can be set between 1 and 168 (one week). * **Cron** — Refresh on a custom cron schedule. The minimum frequency is once per hour. The modal validates the cron string in real time and shows a human-readable description (e.g., `0 0 */2 * *` → "at 12:00 AM, every 2 days"). You can also refresh a dashboard manually at any time using the **Update** button on the dashboard view (see [Refreshing a Dashboard](#refreshing-a-dashboard)). ## The Dashboard Editor Dashboards are built from a stack of blocks. Click **Edit** on a dashboard to open the editor. The editor has three areas: * **The block list** on the left lets you reorder blocks via drag-and-drop. Hovering over a block reveals quick actions for editing, duplicating, and deleting. * **The dashboard canvas** in the middle shows the live preview of the dashboard with all blocks rendered. * **The configuration sidebar** on the right shows settings for the currently selected block (data source, chart type, dimensions, etc.). Edits go into a **draft state** — none of your changes are persisted until you click **Save**. Use **Cancel** to discard the draft and return to the published version. The editor also keeps the rest of your in-flight work in memory, so you can switch blocks without losing changes. ### Adding Blocks Click **Add Block** at the bottom of the block list (or the `+` button between two existing blocks) and pick a type from the dropdown. The dropdown is grouped to make blocks easy to find. ## Dashboard Blocks The following block types are available on Product Analytics Dashboards. Each block has its own configuration sidebar with controls tailored to that block type. | Block | Purpose | | :- | :- | | **Markdown** | Add formatted text, links, and images for context, headings, summaries, or section dividers. | | **Metric Explorer** | Visualize one or more existing metrics. Same configuration as the [Metric Explorer](/app/product-analytics/explorer). | | **Fact Table Explorer** | Aggregate a fact table by counts, distinct units, or a sum. Configure dimensions and chart type. | | **Data Source Explorer** | Query a timestamped warehouse table without first defining a fact table. | | **Custom SQL Query** | Render saved queries from [Custom SQL Reports](/app/product-analytics/explorer#custom-sql-reports). | ### Markdown blocks Markdown blocks render standard Markdown — headings, lists, links, images, code blocks, and tables. Use them to: * Title sections of the dashboard. * Provide context, methodology, or caveats next to a chart. * Embed images (such as wireframes or product screenshots). * Link out to related Slack threads, Notion pages, or other documentation. ### Explorer blocks The Metric, Fact Table, and Data Source Explorer blocks share the same configuration UI as the standalone [Explorer](/app/product-analytics/explorer). The block sidebar shows: * A condensed toolbar with **Chart Type**, **Date Range**, and (for time-series charts) **Date Granularity**. * The full sidebar — data source, values, row filters, group-by dimensions, and Show As. Each block runs its own query against the warehouse, so a single dashboard can mix data from multiple data sources or fact tables. ### Custom SQL Query blocks A Custom SQL Query block is bound to a saved query from [Custom SQL Reports](/app/product-analytics/explorer#custom-sql-reports). The block can show: * One or more visualizations attached to the saved query (Bar, Line, Area, Scatter, Big Value, or Pivot Table). * The raw results table — useful when the value of the report is the data itself rather than a chart. The order of items in the block matches the order configured on the saved query, and you can reuse the same saved query across multiple dashboards. ## Saving an Exploration to a Dashboard You can build a chart in the Explorer and add it to a dashboard in one step: 1. Configure the exploration in the Explorer. 2. Click **Save to Dashboard** in the top-right of the configuration sidebar. 3. Pick an existing dashboard or create a new one. This saves the exploration as a **Metric Explorer**, **Fact Table Explorer**, or **Data Source Explorer** block on the destination dashboard. See [Save to Dashboard](/app/product-analytics/explorer#save-to-dashboard) for the full details on the modal. ## Refreshing a Dashboard Each dashboard has an **Update** button in the top right. When you click **Update**, GrowthBook re-runs every block's query against the warehouse. While the refresh is in progress, you'll see: * A **progress bar** under the button showing how many of the queries have finished. * A live status text — `Updated 5m ago`, `One or more queries failed`, or `Update Failed` — with a tooltip describing the error if one occurred. If auto-update is enabled, a **lightning icon** appears next to the status, and the tooltip shows when the next automatic refresh will run. > [!NOTE] > > Refreshing the dashboard requires the **Create Analyses** permission and read access to every data source the dashboard touches. ## Sharing and Collaboration > [!NOTE] > > Only **Enterprise** organizations can publish dashboards for organization-wide viewing. On **Pro** plans, dashboards are limited to private (owner-only) view and edit access. Once a dashboard is published to the organization, anyone with view access can: * Open the dashboard. * See the latest auto-refreshed data (or trigger a manual refresh). * Click **Share...** to copy a direct link. To control sharing on an existing dashboard, use the **More** menu (⋯) on the dashboards list and choose **Share...**. Only the owner and organization admins can change view and edit access. ### Reassigning Ownership Editing a dashboard you own (or that you administer) lets you change the **Owner**. Reassigning ownership is useful when a teammate leaves the company or hands a dashboard off to a different team. ### Duplicating Dashboards Use the **Duplicate** option in the **More** menu to create a copy of an existing dashboard. This is useful when you want to use one dashboard as a template for another (for example, copying a "Marketing KPIs" dashboard to start a "Growth KPIs" version). Duplicates inherit all blocks but are owned by the user who duplicated the dashboard. ## Permissions Dashboard actions are controlled by per-project permissions: | Action | Required permission | | :- | :- | | View a dashboard | View access on the dashboard + access to its projects | | Create a dashboard | Create General Dashboards | | Edit a dashboard | Update General Dashboards (and edit access on the dashboard) | | Refresh dashboard data | Create Analyses + Run SQL Explorer Queries on each data source | | Delete a dashboard | Delete General Dashboards (only the owner or org admins) | | Change view/edit access | Owner or organization admin | | Share dashboards across the org | Enterprise plan (Share Product Analytics Dashboards feature) | Private dashboards (where edit access is set to **Owner only**) can only be edited and deleted by the owner and organization admins, regardless of other permissions. See [User Permissions](/account/user-permissions) for the full set of dashboard-related permissions. ## Best Practices * **Group by project** — Scope dashboards to a project so the right people see them in the dashboards list and so they show up under the project filter. * **Lead with markdown** — Use a Markdown block at the top of the dashboard to explain the dashboard's purpose, the data sources it uses, and any caveats. Stakeholders shouldn't have to guess what they're looking at. * **Mix explorations and SQL** — Cover the common cases with Metric and Fact Table explorers, and drop into Custom SQL Reports for the long-tail questions that don't fit a structured explorer. * **Keep auto-update enabled** — Pick a refresh cadence that matches your team's data freshness needs (6 hours for daily standups, nightly cron for weekly reviews) so stakeholders always see fresh data when they open the dashboard. * **Reuse saved queries** — A single saved SQL query can be referenced by many dashboards. Update the query once and every dashboard that links to it gets the new logic. # Explorer Source: https://docs.growthbook.io/app/product-analytics/explorer Build ad-hoc charts from your metrics, fact tables, and raw warehouse data. Use AI Chat or configure explorations manually. The Explorer is an ad-hoc analysis surface for your warehouse data. Use it to chart a metric over time, slice a fact table by a property, query a raw table, or ask a question in natural language. ## Choosing an Explorer Open **Product Analytics** in the left sidebar. From the landing page, you can: * **Ask a question with AI Chat** — Type a question like "How many signups did we have last week, broken out by country?" and let GrowthBook generate the exploration for you. * **Choose an explorer manually** — Pick the type that matches the shape of your data: | Explorer | When to use it | | :- | :- | | **Metrics** | Visualize one or more existing GrowthBook metrics over time. | | **Fact Table** | Aggregate a fact table — counts, distinct units, or a sum of a numeric column. | | **Data Source** | Query any timestamped table in your warehouse without first defining a fact table. | | **Funnel** | Measure how many users complete an ordered sequence of steps and where they drop off. See [Funnels](#funnels). | | **SQL** | Write your own `SELECT` query, then filter, aggregate, and chart the result. See [SQL Explorer](#sql-explorer). | Funnel and SQL Explorer are also available from the Product Analytics sidebar. Each explorer opens with the same layout: a chart on the left, a configuration sidebar on the right, and a toolbar above the chart for chart type, date range, and granularity. SQL Explorer adds a **Build Dataset** step before that layout — see [SQL Explorer](#sql-explorer). ![Explorer with chart and configuration sidebar](https://mintcdn.com/growthbook-ea15456d/hKkmiLOwhH6E2xcD/static/images/product-analytics/explorer-overview.png?fit=max\&auto=format\&n=hKkmiLOwhH6E2xcD\&q=85\&s=972787aae79ac5903a591f6772e74a73) ## Configuring an Exploration The sidebar controls everything that goes into the chart. Whenever you change a setting, the chart goes "stale" — an amber dot appears next to the **Update** button. Click **Update** to re-run the query with the new configuration. ### Data Source Pick the data source you want to query from the dropdown at the top of the chart area. The dropdown is filtered to data sources you have permission to query in the current project. If you change the data source mid-exploration and you've already configured values, GrowthBook prompts for confirmation since switching clears your current dataset. The Fact Table and Data Source explorers only show tables that belong to the selected data source. ### Values Values are the things being measured. The available value types depend on which explorer you're in. #### Metric Explorer Add one or more existing fact metrics. Metrics are grouped in the dropdown: * **Official Metrics** — metrics managed by GrowthBook or imported from a [managed source](/app/metrics). * **Other** — metrics created and edited directly in your organization. You can chart multiple metrics on the same exploration as long as they're *compatible*: * **Ratio metrics** can't be combined with non-ratio metrics in the same chart. * **Quantile metrics** can't be combined with other metric types. * All other metric types can be mixed freely. Incompatible metrics show up grayed out in the dropdown with a tooltip explaining the restriction. #### Fact Table Explorer Pick a fact table from the dropdown, then add one or more values. Each value has: * **Value type** — `Unit Count` (distinct users), `Count` (rows), or `Sum` of a numeric column. * **Value column** (when value type is `Sum`) — choose which numeric column to sum. Only `number`-typed columns from the fact table are listed. #### Data Source Explorer Pick a table from your warehouse, then choose its **Timestamp Column** — GrowthBook will try to infer this from the column types, but you can override it. After that, the value configuration is the same as the Fact Table explorer (`Unit Count`, `Count`, or `Sum`). If you haven't built an information schema for the data source yet, the explorer prompts you to generate one. Schema building runs in the background; if it takes more than a few minutes, you're free to leave the page and come back later. ### Row Filters Each value in the Fact Table and Data Source explorers can have its own row filters to narrow which events contribute to it. ![Row filters on a value card](https://mintcdn.com/growthbook-ea15456d/hKkmiLOwhH6E2xcD/static/images/product-analytics/explorer-filter.png?fit=max\&auto=format\&n=hKkmiLOwhH6E2xcD\&q=85\&s=f4fe48000695f20bc9b87cb4df20b5a0) A filter has three parts: 1. **Column** — a column on the fact table or data source. JSON sub-fields appear as `column.field`. You can also pick `SQL Expression` to write a raw SQL `WHERE` clause, or `Saved Filter` (Fact Tables only) to reuse a filter defined on the fact table. 2. **Operator** — depends on the column's data type. Common operators include `=`, `!=`, `in`, `not_in`, `is_null`, `not_null`, `>`, `<`, `>=`, `<=`, `contains`, and `like`. Boolean columns get `is_true` / `is_false`. 3. **Value** — for `in` / `not_in` operators, you can enter multiple values; for `=` / `!=`, GrowthBook will surface the column's top values in a dropdown so you don't have to type them. Operators like `is_null`, `not_null`, `is_true`, and `is_false` don't take a value. Filters can be **disabled** without deleting them (useful while iterating) and **collapsed** to save space. Each filter card shows a one-line summary like `country in US, CA` so you can scan the list at a glance. ### Date Range Choose how far back the query should look: * **Today** — events from midnight today. * **Past 7 / 30 / 90 Days** — rolling windows ending now. * **Custom Lookback** — look back *N* hours, days, weeks, or months from now (e.g., "last 14 days"). * **Custom Date Range** — pick explicit start and end dates. ### Group By Add one or more dimensions to break the chart down. The Explorer supports four dimension types: * **Date** — Bucket by hour, day, week, month, or year. The date dimension is added automatically for time-series chart types. * **Dynamic** — Pick a column and let GrowthBook surface the top *N* values automatically. **Max values** defaults to 5 and can be set between 1 and 20 in the dimension's **Advanced Options**. Other rows are grouped into "Other". * **Static** — Pick a column and specify the exact values you want to compare. Anything outside the list is excluded. * **Slices** — Define named groups using row filters (for example, "New Users" vs. "Returning Users"). Each slice gets its own series on the chart. The number of group-by dimensions you can add depends on the chart type — for example, **Big Number** charts don't accept any dimensions, while stacked bar charts accept multiple. Columns already used by another dimension are filtered out of the column dropdown to prevent duplicates. ### Show As For metrics that have a denominator (such as ratio or proportion metrics), the **Show As** section appears under Group By and lets you choose how the value is rendered: * **Event Totals** — Render the raw numerator (e.g., total purchases). * **Per Unit** — Divide numerator by denominator (e.g., purchases per active user). The label changes to match the unit (e.g., "Per anonymous\_id"). Ratio metrics that are defined as numerator/denominator always render as a ratio regardless of this setting. ### Chart Type Switch between visualizations from the toolbar. The selector is grouped by chart family: | Group | Chart | | :- | :- | | Time Series | **Line**, **Area**, **Time Series Table** | | Cumulative | **Bar**, **Bar (Stacked)**, **Horizontal Bar**, **Horizontal Bar (Stacked)**, **Table**, **Big Number** | A few rules apply automatically: * The **Granularity** selector only appears for time-series charts. * **Big Number** doesn't accept multiple values, dimensions, or group-by columns. * The chart type drives which dimensions are useful — for example, stacked bar charts need at least one group-by dimension to be meaningful. ### Granularity For time-series charts, **Granularity** controls how dates are bucketed: `Auto`, `By Hour`, `By Day`, `By Week`, `By Month`, `By Year`. `Auto` picks a sensible bucket based on the date range (e.g., a 7-day window auto-selects `By Day`, a 1-year window auto-selects `By Week`). Granularities that would produce too many or too few buckets for the selected date range are hidden from the dropdown. ## Funnels The Funnel explorer measures how many users complete a sequence of steps and where they drop off. Use it to answer questions like "of the users who viewed a product, how many added it to a cart, and then checked out?" Pick **Funnel** from the explorer list. A funnel uses the same chart-and-sidebar layout as the other explorers, but instead of a list of values you configure an ordered list of **steps**. ### Steps A funnel is an ordered list of steps, and it needs at least two. Click **Add step** to append one. Each step has: * **Name** — Double-click the step header to rename it. Defaults to `Step N`. * **Fact table** — Where the step's events come from. The first step sets the funnel's fact table; each later step **inherits from the previous step** and can **Override fact table** to point somewhere else. This lets one funnel span multiple fact tables, for example a `views` table for step 1 and an `orders` table for step 2. * **Row filters** — Applied per step to define what counts as completing it, such as `event_name = add_to_cart`. These work exactly like [Row Filters](#row-filters) elsewhere in the Explorer. ### User identifier A funnel counts unique units that move through the steps in order, matched by a single identifier that exists on **every** step's fact table. If the steps don't share an identifier type, GrowthBook shows "No shared user identifier across steps" and the funnel won't run until the steps have one in common. > [!TIP] > > The identifier doesn't have to be a user. If your fact tables share a session identifier, counting by it turns the funnel into a per-session funnel. To use a value that lives inside a JSON column, expose it as its own identifier column in the fact table's SQL first. ### Optional steps and conversion windows Steps after the first have an **Advanced Options** section: * **Optional step** — Users who skip this step can still convert to later steps. Use it for steps that aren't required to continue. * **Conversion window** — How long after the previous step a user has to complete this step and still count. Enter a number and a unit: `minutes`, `hours`, `days`, or `weeks`. With no conversion window, a step counts as long as it happens after the previous one. ### Breaking down a funnel Add a dimension in the **Group By** section to split the funnel by a property, such as country or plan. Funnels use a single, first-touch dimension: each unit is bucketed by the dimension value on its first step. ### Reading the results Switch between two views from the toolbar: * **Funnel** — One bar per step, with the drop-off to the next step shown behind it. * **Funnel Table** — The same data in tabular form. The y-axis toggle controls how each step is measured: * **Unit Count** — The number of units that reached the step. * **Percent** — The share of the starting cohort that reached the step. Each step after the first also reports the average time to convert from the previous step. ### Supported data sources Funnels run on Postgres, ClickHouse (including the [Managed Warehouse](/app/managed-warehouse)), BigQuery, Snowflake, Athena, Presto, Databricks, and Redshift. > [!NOTE] > > Vertica, MySQL, and MSSQL are not yet supported for funnels. The Funnel explorer is hidden for data sources it can't query. ### Saving and sharing Like any other exploration, a funnel can be [saved to a dashboard](#save-to-dashboard) or [shared as a link](#share-a-link). ## SQL Explorer The SQL Explorer is for analysis that doesn't fit a structured metric, fact table, or data source exploration. Write a `SELECT` query against your warehouse, then use the same chart, filter, and group-by controls as the other explorers. Open **Product Analytics → SQL Explorer** in the left sidebar. > [!NOTE] > > If your organization still has saved queries from [Custom SQL Reports](#custom-sql-reports), a banner on this page links to those legacy reports. Use the SQL Explorer for new queries. ### Build Dataset The SQL Explorer has two tabs. Start on **Build Dataset**: * **Data source** — pick which warehouse connection to query. * **Schema Browser** — browse databases, schemas, tables, and columns in the sidebar. * **SQL editor** — write a read-only `SELECT` (or `WITH` … `SELECT`) query. Autocomplete, **Format**, and optional AI assistance are available. AI requires the AI Suggestions commercial feature. * **Run Query** — tests the query and previews the first **100 rows**. Results can be downloaded as CSV. Write a query that returns the rows you want to analyze. Include a date or timestamp column if you want date filtering, period comparisons, and time-series charts. This query should be as generic as possible - filtering and aggregation will be applied to it automatically via the **Build Dataset** step. > [!NOTE] > > The SQL Explorer enforces read-only queries. The query is rejected if it doesn't start with `SELECT` or `WITH` (after stripping comments and string literals). > [!WARNING] > > **Redshift and Microsoft SQL Server** > > A query that uses a CTE (`WITH` clause) can fail when GrowthBook wraps it for filtering and aggregation — these warehouses don't support the resulting nested `WITH`. Use inline subqueries or a database view instead. ### Explore Dataset After a successful test query, switch to **Explore Dataset**. The chart and sidebar match the other explorers: * **Timestamp column** — optional. GrowthBook infers one from date or timestamp columns when possible. Leave it unset for queries that aren't time-based; date range, comparisons, and time-series charts stay disabled until you pick one. * **Values** — `Count` (rows) or `Sum` of a numeric column. Add as many as you need. * **Row filters, Group By, Chart Type, and Granularity** — same as [Configuring an Exploration](#configuring-an-exploration). You can switch back to **Build Dataset** at any time to edit the query. Changing the SQL and re-running it updates the columns available in Explore Dataset. Like any other exploration, a SQL exploration can be [saved to a dashboard](#save-to-dashboard) or [shared as a link](#share-a-link). ## Running and Refreshing When the configuration is valid, click **Update** to run the query. A clock icon next to the toolbar shows when the chart was last refreshed (e.g., "5m ago"). Click it to open a dropdown with a **Refresh** option to manually re-run the query, even if the configuration hasn't changed. GrowthBook caches exploration results: re-opening the same exploration with the same configuration uses the cached result rather than re-querying the warehouse. Forcing a refresh ignores the cache and runs a fresh query. ## Saving and Sharing Once an exploration looks the way you want it, you have a few options: ### Save to Dashboard Click **Save to Dashboard** to add the exploration as a block on a [Product Analytics Dashboard](/app/product-analytics/dashboards). You'll be asked for: * **Chart Title** — required. * **Save to** — pick an existing dashboard or create a new one. The existing dashboard list is filtered to dashboards you have permission to edit. * (When creating new) **Name**, **Projects**, and **Advanced Settings** — the same view/edit access, auto-update, and schedule controls available when [creating a dashboard from scratch](/app/product-analytics/dashboards#dashboard-settings). Saving requires the **Product Analytics Dashboards** commercial feature and permission to create or edit dashboards in the target project. ### Share a Link The link icon next to **Save to Dashboard** copies a URL that encodes the full exploration configuration. Anyone in your organization with read access to the underlying data source can open it. > [!TIP] > > Links are tied to the explorer type. A link generated from the **Metric** explorer can only be opened in the **Metric** explorer. If you open a link in the wrong explorer, the Explorer falls back to default settings and tells you what happened. ## AI Chat The AI Chat panel at `/product-analytics/explore/ai-chat` lets you ask questions about your metrics and data in natural language. It uses the same building blocks as the manual explorers, so any chart it produces can be tweaked, saved to a dashboard, or shared. ![AI Chat with conversation history and an embedded chart](https://mintcdn.com/growthbook-ea15456d/hKkmiLOwhH6E2xcD/static/images/product-analytics/explorer-ai-chat.png?fit=max\&auto=format\&n=hKkmiLOwhH6E2xcD\&q=85\&s=33b5a5b4f83400e4135d237797df542a) ### What it can do The AI agent has access to a small set of tools and will narrate what it's doing as it works: * **Search** the data sources, fact tables, and metrics available to you. * **Inspect data shape** — list available columns and column types on a table. * **Inspect values** — sample top values for a column. * **Run an exploration** — produce a chart inline in the conversation. You can click into any chart to open it in the Explorer for further editing. * **Get a snapshot** — pull recent values when reasoning about your data. ### Conversations and history Conversations are persisted, so you can come back later and pick up where you left off. The sidebar lists previous conversations and lets you start a new chat at any time. While the agent is generating a response, you can cancel mid-stream. You can also choose which model to use from the dropdown — GrowthBook defaults to the model configured in **Settings → General → AI**. ### Requirements AI Chat requires both: * **AI features enabled** for your organization (configured under **Settings → General → AI**). * The **AI Suggestions** commercial feature on your plan. If either is missing, the AI Chat input is disabled with an inline explanation. ## Custom SQL Reports > [!WARNING] > > **Custom SQL Reports are deprecated** > > Custom SQL Reports at `/sql-explorer` have been replaced by the [SQL Explorer](#sql-explorer). Use that for new queries and visualizations. > > Existing saved reports still work. Open **Product Analytics → SQL Explorer** and follow the banner to view them, or go directly to `/sql-explorer`. The Custom SQL Reports page lists saved queries in your organization, scoped to the current project. You can still open an existing report, re-run it, and edit its visualizations, but you should not create new reports there. ### Writing a query Clicking **New SQL Report** opens the legacy SQL Explorer modal. You get: * **A schema browser** showing databases, schemas, tables, and columns for the selected data source. * **A SQL editor** with auto-complete on table and column names, and a "Format SQL" button (when supported by the dialect). * **A results panel** that shows the first 1,000 rows along with the rendered SQL and query duration. Results can be downloaded as CSV. * **Optional AI assistance** for generating or modifying SQL (requires the AI Suggestions commercial feature). > [!NOTE] > > The legacy SQL Explorer enforces read-only `SELECT` queries. The query is rejected if it doesn't start with `SELECT` or `WITH` (after stripping comments and string literals). Results are capped at **1,000 rows**. The [Redshift and Microsoft SQL Server CTE limitation](#sql-explorer) also applies when a saved report is used in a Product Analytics visualization. ### Visualizations Once a query has run successfully, switch to a visualization tab to render the results as a chart. Saved queries can have any number of visualizations attached. Available chart types are: * **Bar** * **Line** (with an option to anchor the y-axis to zero) * **Area** * **Scatter** * **Big Value** — a single number with formatting options (`shortNumber`, `longNumber`, `currency`, `percentage`, `accounting`). * **Pivot Table** Each visualization configures its own X axis (or X axes for pivot tables), Y axis, optional dimensions, and chart-specific filters. ### Saving and reusing Saving a query requires the **Save SQL Explorer Queries** commercial feature, available on Pro and Enterprise plans. Existing saved queries can be: * Re-opened from the Custom SQL Reports list. * **Linked to dashboards** via the deprecated **Custom SQL Query** block type. A single saved query can power multiple dashboards. New dashboard blocks should use an [SQL Explorer](#sql-explorer) exploration instead. ## Permissions The actions available in the Explorer depend on your role and project assignments: | Action | Required permission | | :- | :- | | Use the Metric explorer | Run Metric Queries | | Use the Fact Table explorer | Run Fact Queries | | Use the Data Source explorer | Run Fact Queries + Run Schema Queries | | Use the SQL Explorer | Run SQL Explorer Queries | | Use Custom SQL Reports | Run SQL Explorer Queries | | Save Custom SQL Reports | Create SQL Explorer Queries (legacy; Pro or Enterprise plan) | | Save explorations to dashboards | Create / Update General Dashboards (Pro or Enterprise plan) | See [User Permissions](/account/user-permissions) for a full breakdown of what each role can do. # Query Optimization Source: https://docs.growthbook.io/app/query-optimization Cut warehouse cost and runtime with date filters, template variables, Pipeline Mode, and other SQL optimization settings. GrowthBook is designed to run efficient database queries out-of-the-box, but for large datasets or complex metrics, there are a few settings and techniques you can use to optimize your queries. ## SQL Template Variables GrowthBook runs all SQL through a templating engine (Handlebars) to allow for dynamic SQL generation. This lets you apply advanced query optimization. The most common is adding a date filter. For example: ```sql theme={null} SELECT timestamp, user_id, amount FROM purchases WHERE timestamp BETWEEN '{{startDate}}' AND '{{endDate}}' ``` Read more about [SQL Templates](/app/sql-templates) and the different variables that are available. ## Fact Tables Fact Tables are a shared SQL definition that can be re-used across multiple metrics. For example, a "Purchases" fact table could be used for both a "Total Revenue" metric and a "Items per Order" metric. If you are still using legacy metrics (where each metric has its own separate SQL definition), you are missing out on important query optimizations and the newest features in GrowthBook. Read more about [Fact Tables](/app/metrics) and how to [convert legacy metrics to Fact Tables](/app/metrics/legacy#migrating-legacy-metrics-to-fact-tables). ## Fact Table Query Optimization Enterprise **Multi Metric Queries** is available on Enterprise plans. Fact Table Query Optimization enables faster, more efficient queries. If multiple metrics from the same Fact Table are added to an experiment, they will be combined into a single SQL query. For data sources with usage-based billing, this can result in dramatic cost savings. There are some restrictions that limit when this optimization can be performed: * Ratio metrics where the numerator and denominator are part of different Fact Tables are always excluded from this optimization * If `Exclude In-Progress Conversions` is set for an experiment, optimization is disabled for all metrics * If you are using MySQL and a metric has percentile capping, it will be excluded from optimization In all other cases, this optimization is enabled by default. It can be disabled under **Settings → General → Experiment Settings**. When disabled, a separate SQL query will always be run for every individual metric. ## Data Pipeline Mode Enterprise **Pipeline Mode** is available on Enterprise plans. Data Pipeline Mode reduces the amount of duplicate data your warehouse needs to scan. When enabled, GrowthBook will write some intermediate tables back to your warehouse with short retention and re-use those across all of the metric queries in an experiment. Currently, this is limited to BigQuery, Snowflake, and Databricks, but we are working on adding support for other data sources soon. Read more about [Data Pipeline Mode](/app/data-pipeline). ## Materialized Views If your metric definitions are complex and involve multiple joins or subqueries, you may want to consider creating a materialized view in your warehouse. Setting up materialized views differs by warehouse, so consult the documentation for your specific warehouse for more information. You can also use a tool like [dbt](https://www.getdbt.com/) to create computed tables that are automatically refreshed on a schedule. ## Pre-Aggregated Tables Pre-aggregated tables include a GROUP BY in the data pipeline to compress raw event-level data down to fewer rows. This is usually done when querying raw data directly is prohibitively expensive. GrowthBook supports pre-aggregated tables as long as they satisfy two requirements: * Must be grouped by both user and date * Pre-aggregated columns can only be basic sums or counts. No averages, percentiles, count distinct, or complex derived formulas that break statistical assumptions. Using pre-aggregated tables for experimentation comes with additional complexities and downsides, so we highly recommend sticking with event-level data whenever possible. Read more about this approach with some [examples and best practices](/app/metrics/examples#pre-aggregated-tables). # SQL Templates Source: https://docs.growthbook.io/app/sql-templates Insert start and end dates, experiment ids, and other variables into SQL so warehouse queries stay efficient. SQL in GrowthBook is first run through a templating engine (Handlebars) to allow for dynamic SQL generation. This allows for several advanced use cases, outlined below. ## Date Filters GrowthBook always includes a date filter in the SQL queries it generates, but because the queries are complex with multiple nested subqueries, database engines are not always able to fully take advantage of these filters for optimization. If you have a date-partitioned table, you can use template variables within the SQL you enter in GrowthBook to provide better hints to your database. This applies to metrics, fact tables, and experiment assignment queries. Here's an example of a simple Fact Table definition that uses template variables: ```sql theme={null} SELECT timestamp, user_id, amount FROM purchases WHERE timestamp BETWEEN '{{startDate}}' AND '{{endDate}}' ``` The following variables are available: * **startDate** - `yyyy-MM-dd HH:mm:ss` of the earliest data that needs to be included * **startDateISO** - `yyyy-MM-dd'T'HH:mm:ss.SSS'Z'` of the startDate in ISO format * **endDate** - `yyyy-MM-dd HH:mm:ss` of the latest data that needs to be included * **endDateISO** - `yyyy-MM-dd'T'HH:mm:ss.SSS'Z'` of the endDate in ISO format There is also a `date` helper you can use with the ISO variables to format the date exactly how you need it. For example `{{date endDateISO "yyyyMMdd"}}` | code | meaning | | - | - | | yyyy | year | | MM | month | | dd | day | | HH | hour | | mm | minutes | | ss | seconds | | t | timestamp | Here's a full example of this being used: ```sql theme={null} SELECT user_id as user_id, received_at as timestamp FROM orders WHERE partition_key BETWEEN '{{date startDateISO "yyyyMMdd"}}' AND '{{date endDateISO "yyyyMMdd"}}' ``` > [!NOTE] > > The inserted values do not have surrounding quotes, so you must add those yourself (e.g. use `'{{ startDate }}'` instead of `{{ startDate }}`). ## Experiment Id Filter When a Fact Table or Metric query is run as part of an experiment analysis, the `{{ experimentId }}` variable is replaced with the experiment's tracking key. This is useful when a single Fact Table contains rows for many experiments and you need to scope it to the one being analyzed: ```sql theme={null} SELECT user_id, timestamp, score FROM classifier_scores WHERE sample_type = '{{ experimentId }}' ``` When the same SQL is run outside of an experiment context (for example, when previewing a Fact Table or running a standalone Metric Analysis), `{{ experimentId }}` is replaced with `%`. If you need the query to return rows in both contexts, write the filter as a `LIKE` pattern so `%` can act as a wildcard: ```sql theme={null} WHERE sample_type LIKE '{{ experimentId }}' ``` If you prefer an exact match in experiment context and no filter outside experiment context, you can conditionally add the clause with `{{#if ...}}`. Since `%` is always non-empty, this example uses `replace` to strip `%` first: ```sql theme={null} WHERE 1=1 {{#if (replace experimentId "%" "")}} AND sample_type = '{{ experimentId }}' {{/if}} ``` ## Phase Filters Experiments can have multiple phases. By default, we use date ranges to restrict data to the current phase, but this may be imprecise if your experiment configs are cached in your app or data is delayed. For more accurate filtering, we expose a `phase.index` template variable. This is a 0-based index of the current phase, so the first phase is `0`, the second phase is `1`, etc. Here is an example Experiment Assignment Query that is using a phase filter. The phase may not always be available (e.g. when fetching data for an entire experiment), so it must be wrapped in a conditional check: ```sql theme={null} SELECT user_id, timestamp, experiment_id, variation_id FROM experiment_assignments WHERE timestamp BETWEEN '{{startDate}}' AND '{{endDate}}' {{#if phase.index}} AND phase_index = {{phase.index}} {{/if}} ``` > [!NOTE] > > The filter will still be added above even when the phase index is `0`, despite that being a falsy value in most languages. The only time it will not be included is when there is no phase information at all (i.e. it is undefined). ## Custom Field Filters Enterprise **Custom Metadata** is available on Enterprise plans. Custom Fields for experiments can be used in SQL templates to add additional filtering logic. For example, if you have a Custom Field called `region`, you can use that in your Fact Table definition to filter data based on the region for a specific experiment. Like phases, Custom Fields are not always defined, so you must wrap them in a conditional block. Here's an example: ```sql theme={null} SELECT user_id, timestamp, event_type FROM events WHERE 1=1 {{#if customFields.region}} AND region = {{sqlstring customFields.region}} {{/if}} ``` The `1=1` part is a common SQL trick to make appending additional `AND` conditions easier. When the Custom Field is unavailable, the WHERE clause will simply be `WHERE 1=1`, which is always true and does not filter out any data. > [!NOTE] > > Because Custom Field values are user-supplied, use the `sqlstring` helper (e.g. `{{sqlstring customFields.region}}`) to safely quote string values. It wraps the value in single quotes and escapes any embedded single quotes, preventing SQL injection and query errors on values like `it's`. Do not manually wrap the value in quotes (e.g. `'{{customFields.region}}'`) as this is unsafe. # Sticky Bucketing Source: https://docs.growthbook.io/app/sticky-bucketing Keep users in the same experiment variation when you change targeting, rollout, or sticky bucket settings mid-test. Pro **Sticky Bucketing** is available on Pro and Enterprise plans. This article serves two purposes: * High level overview of GrowthBook's Sticky Bucketing feature * Technical details on how to implement Sticky Bucketing in your codebase Sticky bucketing ensures users continue to see the same variation when you make changes to a running experiment. GrowthBook's flavor of sticky bucketing has a few additional features:(1) Bucketing based on either a primary hash attribute (i.e. user id) or a secondary attribute (i.e. anonymous id) and (2) the ability to version-control and purge your users' assigned buckets. ## Motivation So why would you want to use Sticky Bucketing? Let's look at a few examples: 1. You are managing an experiment rollout and need to slow down enrollment. You decrease the percentage of traffic exposed to the experiment from 50% to 10% but you do not want to alter the experience for users who were already exposed to the experiment. Sticky bucketing allows you to apply the rollout percentage to new users while keeping the old users in their original buckets. 2. You discovered a bug in your experiment the day after launching. You fix the bug and want to re-start the experiment, but don't want to include any "tainted" users who saw the buggy version since their negative experience might impact the results. 3. You have a cross-platform app and want to ensure a consistent experience as users log in and move between devices. ## Setting up Sticky Bucketing with the Script Tag SDK ### 1. Configure the script tag Using Sticky Bucketing with our [HTML Script Tag](/lib/script-tag) is simple. Add either of these properties to your script tag: * `data-use-sticky-bucket-service="cookie"` or * `data-use-sticky-bucket-service="localStorage"` The sticky bucket assignments will be stored in either cookies or browser LocalStorage, respectively. Both of these implementations are session-based, but cookies are transportable and are generally more commonly used. For example: ```html theme={null} ``` See the [HTML Script Tag sticky bucketing docs](/lib/script-tag#sticky-bucketing). ### 2. Enable Sticky Bucketing for your organization In the GrowthBook app, go to **Settings** → **General** → **Experiment Settings** and enable the Sticky Bucketing toggle. This will add new options specific to Sticky Bucketing whenever you make changes to a running experiment. To read more about these options, see [Experiments (setup)](/app/experiment-configuration). Once Sticky Bucketing is enabled, there is an additional toggle for enabling a **Fallback Attribute**. See below for more information on this feature. ## Setting up Sticky Bucketing with other SDKs To use Sticky Bucketing for your experiments, there are a few steps that you need to complete. ### 1. Ensure you are using a compatible SDK version Update your codebase to use a compatible SDK. Sticky Bucketing is currently supported in most SDK languages. See "stickyBucketing" availability in the [list of SDKs and Capabilities](https://github.com/growthbook/growthbook/blob/main/packages/shared/src/sdk-versioning/CAPABILITIES). ### 2. Pass a Sticky Bucketing Service into your SDK Implementation You may use one of our built-in Sticky Bucketing Services or implement your own. We provide common drivers for browser-generated cookies, backend-generated cookies, browser LocalStorage, and Redis stores. For more information about setting up Sticky Bucketing at the SDK level, see the appropriate SDK documentation. For instance, see the [JavaScript SDK - Sticky Bucketing documentation](/lib/js#sticky-bucketing) for more information about setting up Sticky Bucketing in the JavaScript SDK. ### 3. Update your SDK Connections in the GrowthBook app Within the GrowthBook app, ensure that your SDK Connections are configured correctly. You can do this by going to the **SDK Connections** page, clicking into a connection, and clicking the **Edit** button in the top right. Make sure the connection (1) only has a single language selected and (2) has the correct SDK version specified. ![Setting the SDK Connection Version](https://mintcdn.com/growthbook-ea15456d/J3C3juKhu0f7KEr_/static/images/sdk-connection-version-sb.png?fit=max\&auto=format\&n=J3C3juKhu0f7KEr_\&q=85\&s=14cec9a5bef7b3e6b2bb925cfaa319ea) In the above example, React is selected and the version is set to the latest `0.22.0`, which supports sticky bucketing. If you are using GrowthBook with multiple languages, create a separate SDK Connection for each language. ### 4. Enable Sticky Bucketing for your organization In the GrowthBook app, go to **Settings** → **General** → **Experiment Settings** and enable the Sticky Bucketing toggle. This will add new options specific to Sticky Bucketing whenever you make changes to a running experiment. To read more about these options, see [Experiments (setup)](/app/experiment-configuration). Once Sticky Bucketing is enabled, there is an additional toggle for enabling a **Fallback Attribute**. See below for more information on this feature. ## Fallback Attribute Users are assigned an experiment variation based on a **Hash Attribute**, for example a logged-in `userId`. With Sticky Bucketing, you also have the option of specifying a **Fallback Attribute** for an experiment, for example an anonymous `cookieId`. This fallback will be used if the primary hash attribute is missing or empty. ### Fallback Attribute Example Imagine your users tend to sign in on multiple devices. Let's say you want to test changes to the main navigation header of your app, something that is visible to both logged-in and anonymous visitors. If you were to only use `userId` to assign variations, signing in could become a jarring experience - users might flip from seeing the control (since their user id is empty) to seeing the variation (once they log in). On the plus side, if users open your app on multiple devices (when logged in), they will always see a consistent experience. If instead, you were to only use the anonymous `cookieId` to assign variations, it solves the issue where the UI flips during sign in (since the anonymous id stays the same before and after), but now switching devices could become a jarring experience - the same user might get assigned different variations on different devices, since each device would have its own separate anonymous id. Fallback attributes, when implemented properly with sticky bucketing, lets you have the best of both worlds (with some caveats). Your primary hashing attribute would be the logged-in `userId` and your fallback attribute would be the anonymous `cookieId`. The very first variation a user is assigned to will "stick" to them and follow them across devices. So if a visitor lands on your website, gets assigned variation B (from their fallback attribute), and then logs in, they will continue seeing variation B, even though they now have a `userId` attribute. If that same user then logs into your app on a new device, they again will continue seeing variation B. There are 2 caveats with fallback attributes: 1. There are still some scenarios where users will get inconsistent experiences. For example, if they are logged out on two devices, there's no way for us to know they are the same person. 2. It opens you up to potential bias in your experiment results (see more below). ### Bias Risk To understand the risk of bias, let's focus on a user switching devices. During analysis, we will have to use the anonymous `cookieId` as the experimental unit to make sure we capture everyone in the experiment, even those who never logged in. When a user logs in on two devices, they will be seen as two separate "users" in the analysis since each device has its own cookie id. Because of the fallback attribute and sticky bucketing, however, they will both get assigned the same variation. This breaks one of the statistical assumptions of A/B testing - that each user is randomly assigned a variation. Let's see how this might play out to cause bias in your results. Imagine your variation causes people to use multiple devices more often than your control does. 200 people land on your website and get split into control and variation - 100 in each. In the control, 20 of them also log in on their phone, but in the variation 60 of them log in on their phone. In your analysis, you have 280 total anonymous ids and you expect them to be split evenly - 140 each. In reality, the control would have 120 ids while the variation has 160. A difference this extreme is easy to spot in the results (GrowthBook runs Sample Ratio Mismatch tests automatically to catch exactly this type of bug), however there are many similar, but more subtle, issues that may fly under the radar. Bottom line: with Fallback Attributes, you can get a more consistent within-session and cross-device user experience at the expense of statistical rigor. With GrowthBook, we let you decide this trade-off for yourself on a per-experiment basis. ## Example Sticky Bucket Implementations ### Front-end only Suppose your website integrates GrowthBook on the front end only. You would like to implement Sticky Bucketing to protect against variation hopping should targeting or rollout rules change in the future. In our JavaScript and React SDKs, we provide 2 different Sticky Bucket Services that make sense in this scenario: `LocalStorageStickyBucketService` and `BrowserCookieStickyBucketService`. You can instantiate either of these services and plug them into the GrowthBook SDK. If you use `BrowserCookieStickyBucketService` behind a Web Application Firewall, see [Sticky Bucket Cookies and WAF False Positives](/kb/sdks/sticky-bucket-cookie-encoding) — the cookie's default encoding can trigger generic SQL injection rules. ### Front-end and Back-end (Node.js) Let's expand the "front-end only" example above so that our back-end controllers also integrate with GrowthBook and can reference the same experiments. In this scenario, we would like both the front-end and back-end to perform bucketing and persist a sticky bucket that reliably crosses the front-end / back-end divide. On the front end, you will want to use the `BrowserCookieStickyBucketService` because cookies are easily transportable to and from the back end. Then, assuming we are using an Express (NodeJS) server, we would use the `ExpressCookieStickyBucketService` on the back end. Importantly, if customizing the cookie name, you must ensure that the same name prefix is chosen for both the front-end and back-end cookies. ### Back-end only Suppose that in a server-side context we are interested in persisting a user's bucket both across multiple requests and across other back-end (micro)services that may not have direct access to the incoming user request nor their cookies. We could use a Redis instance inside our network and read/write to that for sticky bucket storage. In a NodeJS context, we could use the `RedisStickyBucketService` and pass in an `ioredis` client. ### Hybrid and custom implementations You may wish to employ multiple strategies at once (front-end, back-end, Redis) or write your own sticky bucket connector for a SQL server or DynamoDB cluster. You could write your own custom sticky bucket connector by implementing the `StickyBucketService` interface. Within your connector, you could do things like: * Connect to SQL server for sticky bucket reads/writes * GET/POST/RPC to a custom bucketing microservice * Wrap both the `ExpressCookieStickyBucketService` and `RedisStickyBucketService` within your custom service's getter and setter methods * Trigger side effects on bucket reads/writes # URL Redirect Testing Source: https://docs.growthbook.io/app/url-redirects A/B test alternate URLs without writing assignment code by serving redirects through the GrowthBook SDK. Pro **Redirects** is available on Pro and Enterprise plans. URL Redirect tests are an alternative to using the AI Visual Editor and are ideal for testing big changes or complete page redesigns. ## How it Works You start by specifying an "Original URL". Users who visit this URL will be included in the experiment (assuming they meet all of the other targeting conditions). You then specify "Destination URLs" for each of your variations. You can also turn off redirects for some of your variations if you want to keep the user on the Original URL. When a user visits the Original URL and is included in the experiment, they will be assigned a variation. Then, an "Experiment Viewed" event will be sent to your data warehouse. After a short delay (default `100ms`) to allow for the tracking event to finish, the user will be redirected to the destination URL. ### Redirecting when only query params change Setting up tests where the redirect URL only varies from the original by the query parameters requires special consideration. You'll need to include the query params you plan to use in the original URL with no value entered in order for our URL targeting to work correctly (i.e. if you want to redirect a user to `http://www.growthbook.io/test?abc=123` from `http://www.growthbook.io/test`, your original URL will need to be `http://www.growthbook.io/test?abc`). ## Implementation URL Redirect tests require integrating one of our [SDKs](/lib) into your application. Currently, URL Redirect tests are only supported in our [Script Tag](/lib/script-tag), [JavaScript](/lib/js), and [ReactJS](/lib/js) SDKs—as well as natively supported in all [Edge SDKs](/lib/edge/cloudflare). Verify URL Redirect tests are enabled for your SDK Connection: Go to **SDK Connections**, select your SDK connection, click **Edit**, and ensure that "Enable URL Redirect experiments" is toggled on. ### HTML Script Tag SDK The easiest option is to use our [Script Tag](/lib/script-tag) SDK. This involves adding a single ` ``` ### Front-End JavaScript and React SDKs A more advanced integration involves using our [JavaScript](/lib/js) or [ReactJS](/lib/js) client-side SDKs. Besides the standard implementation described in the SDK docs, there are additional settings to control redirect behavior. * `navigate`: A callback function to perform the redirect. Defaults to `(url) => window.location.replace(url)`. * `navigateDelay`: The number of milliseconds to wait before redirecting. Use this delay to give time for your analytics tracking callback to finish. If your tracking callback is properly implemented as async, then you should set this value as low as possible. Defaults to `100` (0.1 seconds). * `maxNavigateDelay`: The maximum number of milliseconds that navigation will be delayed while any async tracking callback is still in flight. If the tracking callback exceeds this value, `navigate` will be fired. Missed tracking calls are generally the largest contributor to [Sample Ratio Mismatch (SRM)](/using/experimenting#sample-ratio-mismatch-srm). Defaults to `1000` (1 second). *Deprecated:* * `antiFlicker`: Only available in the Script Tag. * `antiFlickerTimeout`: Only available in the Script Tag. #### Single Page Apps (SPAs) If you have a SPA, it's recommended to use your own `navigate` function to avoid full-page redirects. In this case, also set `navigateDelay` to `0`. Here is an example in Next.js: ```jsx theme={null} import router from "next/router"; const gb = new GrowthBook({ navigate: (url) => router.replace(url), navigateDelay: 0, maxNavigateDelay: 0 // only needed if your tracking callback is async // ... other settings }); ``` It's also necessary to update the URL in the GrowthBook instance on every client-side navigation. For example: ```jsx theme={null} router.events.on("routeChangeComplete", (url) => { gb.setURL(url); }) ``` ### Edge SDKs The best practice for running URL redirect tests is to implement them on the backend or edge server (CDN). In a front-end environment, failing to complete the tracking callback before the navigation triggers can lead to substantial [Sample Ratio Mismatch (SRM)](/using/experimenting#sample-ratio-mismatch-srm) errors and could invalidate your experiment results. Similarly, flickering can cause a degraded user experience, which may end up skewing the experiment results in favor of the control (no redirect). We provide native support for edge redirect tests without screen flickering or tracking delays. Learn more about our [Edge SDKs](/lib/edge/cloudflare#turnkey-edge-app). ### Node.js BETA It is possible to use our [JavaScript](/lib/js) SDK to perform redirects on the back-end. You must implement your own `navigate` function. ## Sample Ratio Mismatch (SRM) Warnings on URL Redirect Tests [Sample Ratio Mismatch (SRM) warnings](/using/experimenting#sample-ratio-mismatch-srm) happen when the actual traffic split in an experiment is significantly different from the traffic split configured for the experiment. For instance, if a 50/50 traffic split was configured for an experiment but a significantly different traffic split is observed, like a 46/54 split, an SRM warning will be triggered. GrowthBook only shows SRM warnings if the p-value is less than 0.001, which means it's extremely unlikely to occur by chance. This p-value threshold is customizable in **Settings** → **General** → **Experiment Settings** → **Experiment Health Settings**. ### Why am I seeing SRM warnings on my URL Redirect test? SRM warnings are a common problem when running front-end URL redirect tests, regardless of what experimentation platform is used. When using GrowthBook's [JavaScript](/lib/js), [React](/lib/react), [Vue.js](/lib/vue), or [Script Tag](/lib/script-tag) SDKs to run URL Redirect tests, SRM warnings may occur. The issue is especially pronounced while also using [Google Tag Manager (GTM)](/guide/google-tag-manager-and-growthbook). The inherent latency of the [trackingCallback](/lib/js#experimentation-ab-testing) sometimes causes the URL Redirect to take place before the `trackingCallback` can complete, which means a disproportionately larger number of exposures are tracked for the control URL versus the redirect URL. This triggers an SRM warning. ### What can I do to reduce or eliminate SRM warnings? 1. The best way to eliminate a Sample Ratio Mismatch with URL Redirect tests is to use one of our **Edge Worker SDKs** instead of a front-end SDK. See our Edge SDK documentation for [Cloudflare Workers](/lib/edge/cloudflare), [Fastly Compute](/lib/edge/fastly), and [AWS Lambda@Edge](/lib/edge/lambda). 2. If you opt to use a front-end SDK (JS, React, or Vue), you can still greatly reduce or nearly eliminate SRM by implementing an **async tracking callback**. If your event tracking vendor supports async callbacks, then simply ensure that the `trackingCallback` that you pass into the GrowthBook SDK constructor awaits a successful network request for the tracked event. If you are using our HTML Script Tag, this is automatically handled for *GA4* and *GTM* tracking calls. > [!NOTE] > > **Async tracking callbacks** > > GrowthBook JavaScript and React SDKs introduced **async tracking callbacks** in version `1.2.0`. 3. If you are unable to use an async tracking callback, you can try increasing the `navigateDelay` timeout passed into the GrowthBook SDK constructor. This defers the URL redirection until after a minimum time, increasing the likelihood that your tracking callback is successfully dispatched before page redirection. The default delay is 1000 ms (1 second), however some network conditions or SDK implementations do better with a navigate delay of 2 to 3 seconds. *Segment* recommends a 300 ms delay; if you are using our HTML Script Tag with *Segment*, this is automatically set to 300 ms. # AI Visual Editor Source: https://docs.growthbook.io/app/visual Design, preview, and run A/B tests on your live site from a Chrome side panel — with AI-generated variations or a point-and-click editor, no code required. Pro **AI Visual Editor** is available on Pro and Enterprise plans. GrowthBook's AI Visual Editor is a no-code way to build and launch experiments on your website. Describe the change you want in natural language, review what the editor proposes, and ship a real A/B test without writing code or waiting on a deploy. > [!TIP] > > **Install the extension** > > Get the [GrowthBook AI Visual Editor](https://chromewebstore.google.com/detail/growthbook-visual-editor/nbomejknbpkcpjdagefhichaajpoempk) from the Chrome Web Store, then [connect it to your account](/app/visual/install-and-connect). ![Editing a page in the AI Visual Editor side panel](https://mintcdn.com/growthbook-ea15456d/J3C3juKhu0f7KEr_/static/images/visual-editor/ve-ai-main.png?fit=max\&auto=format\&n=J3C3juKhu0f7KEr_\&q=85\&s=3d88ff2c094a74c641485b5f5527baba) The AI Visual Editor is a **Chrome side-panel extension**. You open it next to your site, pick the page you want to test, and make changes two ways: * **[AI mode](/app/visual/ai-mode)** — describe the change in plain language ("make the hero headline shorter and the CTA green") and the editor proposes the edits for you to preview and accept. * **[Manual mode](/app/visual/manual-mode)** — point and click to select any element and edit its content, typography, layout, background, classes, and more with structured controls. * **[Figma & image import](/app/visual/design-import)** — turn a Figma frame or a mockup image into a high-fidelity, testable component the editor builds and places on the page for you. To actually render your variations on your live site, a developer integrates one of GrowthBook's front-end SDKs — the [Script Tag](/lib/script-tag), [JavaScript](/lib/js), or [ReactJS](/lib/react) SDK — or renders them on a CDN with an [Edge SDK](/lib/edge/cloudflare). See [Running on your site](/app/visual/running-on-your-site). ## Benefits * **Prompt-driven editing**: Describe changes to text, images, buttons, spacing, and more in [AI mode](/app/visual/ai-mode). Pin an element when you want the AI to focus on a specific spot on the page. * **Run experiments without dedicated eng time**: Draft and QA variations in the browser; your site still needs the GrowthBook SDK for production delivery. See [Running on your site](/app/visual/running-on-your-site). * **Manual precision**: Fine-tune typography, spacing, links, and visibility with structured controls when you know exactly what you want. See [Manual mode](/app/visual/manual-mode). * **Transparent by design**: Preview links, a debug panel, and warnings when you edit a live experiment. See [Preview and QA](/app/visual/preview-and-qa). > [!NOTE] > > **Join the AI Visual Editor community on Slack** > > Connect with the GrowthBook team and other users. [Join the AI Visual Editor channel on Slack](https://growthbookusers.slack.com/archives/C0BBMTLJUUT). ## Quick start 1. [Install and connect](/app/visual/install-and-connect) the Chrome extension (\~30 seconds). 2. Open the side panel on the page you want to test and start editing in [AI mode](/app/visual/ai-mode) or [Manual mode](/app/visual/manual-mode). 3. [Set up the SDK](/app/visual/running-on-your-site) on your site so variations render for real users. Hitting a snag? See [Troubleshooting](/app/visual/troubleshooting). ## Requirements Your app must be a **front-end web app in a browser** (not native mobile or desktop). For other platforms, use [Feature Flags](/app/features) in code. > [!WARNING] > > **Client-side rendering can interfere with visual changes** > > Heavily client-side-rendered apps (React, Vue, Svelte) may flicker or fight the SDK when the framework re-renders the DOM. See [Running on your site → Strategy 4](/app/visual/running-on-your-site#strategy-4-consider-whether-visual-testing-is-the-right-tool) for details and alternatives. ### SDK setup Visual experiments only render if your SDK Connection has **Include Visual Experiments** enabled and the correct SDK is installed on your site. See [Running on your site](/app/visual/running-on-your-site#create-an-sdk-connection) for the full setup. # AI Mode: Generate A/B Test Variations With AI Source: https://docs.growthbook.io/app/visual/ai-mode Generate A/B test variations with natural language in the AI Visual Editor. Preview the proposed edits, then accept or change them. Enterprise **AI Suggestions** is available on Enterprise plans. AI mode is the fastest way to draft an A/B test variation in the [AI Visual Editor](/app/visual). You describe what you want in plain language ("make the hero headline more urgent and the primary button green"), and the AI proposes specific DOM-level changes — element selectors, new content, new styles — that you can preview, accept, edit, or reject one at a time. ![The GrowthBook AI Visual Editor](https://mintcdn.com/growthbook-ea15456d/J3C3juKhu0f7KEr_/static/images/visual-editor/ve-ai-chat.png?fit=max\&auto=format\&n=J3C3juKhu0f7KEr_\&q=85\&s=68776b3c785a66ed6e12f84ee972aaf5) It's especially good for: * Multi-property changes ("make this card more premium") without touching each control * Change text content without needing to find the right content field in the property panel * Less technical users who don't want to think about CSS selectors You can mix AI mode and [Manual mode](/app/visual/manual-mode) freely within the same variation — accept the AI's draft, then jump into Manual mode to fine-tune. ## Prerequisites Before AI mode is usable on your account: 1. **An org admin needs to configure an AI provider.** Go to **Settings → AI** in GrowthBook and add an OpenAI, Google, or xAI API key. See [AI Integrations](/integrations/ai) for the full setup. 2. **Your plan must include AI features.** AI mode is part of the `ai-suggestions` commercial feature. Pro and Enterprise plans include it; Free does not. 3. **You need to be connected** to your GrowthBook account in the [AI Visual Editor side panel](/app/visual/install-and-connect). If AI mode is greyed out in the side panel, the most common cause is that no AI provider key is configured yet for the organization. ## Opening AI mode After you open the side panel and target a URL, you'll see two top-level tabs: **AI** and **Manual**. AI is selected by default. The AI panel has three parts: * A **prompt box** at the top of the panel where you describe what you want. * A **chat log** that fills in as you make requests — each turn shows what you asked, followed by an assistant card with the proposed changes. * The **Changes panel** at the bottom of the side panel, which lists every change accepted so far for the current variation. When the chat log is empty, the prompt area also surfaces a few **suggested prompts** and **quick actions** to help you get started. ## Writing good prompts The AI does its best work when prompts are concrete about *what* and *where*, but loose about *how*. **Good:** * `Change the hero headline to something more urgent and benefit-led` * `Make the primary CTA button bright green and a little larger` * `Hide the secondary nav links on the right` * `Swap the hero image for one showing a person using a laptop in a coffee shop` * `Give me three alternative headlines for the product card` **Less good:** * `Make the page better` — too vague; the AI will guess and the result will be unfocused. A few tips that consistently improve results: * **Name the element by what it is, not how it's coded.** "The hero headline" works; ".hero h1" rarely needs to be in the prompt. * **Describe intent.** "More urgent," "more premium," "more playful" gives the AI a direction. * **Ask for variants.** "Give me three options" or "draft a few alternatives" produces multiple suggestions in one turn. * **Chain prompts.** A second prompt can refine the first ("the green is too neon — make it more muted"). When naming an element in text isn't enough, use **Select element** to pin it — see [Adding element context](#adding-element-context-to-a-prompt). > [!TIP] > > **"Set the title" targets the H1** > > If you say something like "set the title to X," the AI targets the visible `

` (or the most prominent heading) — not `document.title`. To change the browser tab title, say so explicitly: "change the page's browser tab title to X." ## Adding element context to a prompt Click **Select element** (`@`) below the prompt box, then click an element on the page. It appears as a chip above the prompt — you describe *what* to change; the chip tells the AI *where*. Pin multiple elements for batch edits; remove a chip with **×**. Use this when the page has repeated or ambiguous targets (two headlines, identical cards, several "Submit" buttons). **Example:** pin the hero headline, then prompt *"Make it more urgent and benefit-led"* instead of *"make the headline more urgent"*. For follow-up prompts on the same element without re-pinning, see [Multi-turn conversations](#multi-turn-conversations). ## Reviewing proposed changes When the AI returns, it adds an assistant card to the chat log. The card contains: * A short summary of what was changed and why. * One or more **diff cards** — each represents a single DOM change (a text edit, a style change, an attribute change, etc.). The diff card shows the element it targets and what's being set. Each proposed change is **previewed live on the page** as soon as it lands — you don't have to accept first to see the result. For each change you can: * **Accept** — the change moves into the Changes panel as part of the current variation. * **Reject** — the preview is reverted and the change is dropped. * **Edit** — open the change in Manual mode to tweak values before accepting. Reject doesn't end the conversation. You can ask for a different version ("more subtle — keep the original color, just bump the size") and the AI will produce a new proposal. ## Selector grounding When the AI proposes a change, it picks a CSS selector and validates it against the actual page DOM. If the selector doesn't match a real element, the change is rejected before it ever reaches you. This matters for two reasons: * **Reliability** — the AI can't propose changes against elements that don't exist or that match nothing. * **Stability** — the AI prefers stable selectors (semantic tags, stable classes, `data-*` attributes) over brittle ones (positional `nth-child` chains, random hash-suffix classes). The variation is more likely to keep working as your site evolves. If a proposal targets the wrong element, say so in the next prompt or pin the correct element and re-ask. ## Multi-turn conversations The chat log keeps the full thread for the current variation. You can layer changes: ``` You: change the hero headline to something more urgent AI: [proposes new headline] You: also make the headline larger AI: [proposes a font-size change on the same element] You: actually undo the size change and just bold it instead AI: [reverts size, proposes font-weight] ``` Each new turn sees the prior turns, so refinements work. If you want a clean slate, start a new variation. ## Image generation If you ask the AI to swap a hero image, generate product imagery, or anything else that requires a new image, AI mode hands off to the image-generation flow. You'll pick from one or more configured image models, optionally use the existing image as a visual reference, and accept the result into the changeset the same way as any other change. See [Images](/app/visual/images) for a full walk-through, including the list of supported image models and which ones accept reference images. ## What gets sent to the AI For each prompt, the back-end sends to the configured AI provider: * Your prompt text * A simplified snapshot of the relevant DOM (typically the elements visible in the viewport, plus any selected element) * The URL of the page being edited * Optionally, a small system prompt that biases the model toward stable, accessible changes Your GrowthBook experiment metadata, metric values, user data, and SDK payloads are **not** sent. The connection uses your org's configured AI provider key — see [AI Integrations](/integrations/ai) for the full data-sent inventory. ## Limits and caveats * **Single-page scope.** AI mode reasons about the page currently loaded in the AI Visual Editor's target URL. It can't propose changes against pages it hasn't seen. * **Selectors can still drift.** Even with selector grounding, large site rewrites or design-system changes can break previously-stable selectors. Run a sanity check on long-running visual experiments after major releases. * **Quality varies by model.** The org admin picks the underlying model in AI Settings — bigger/newer models generally produce better proposals at higher cost. The same prompt will produce different output across `gpt-4o-mini`, `gemini-2.5-pro`, and `grok-2`. * **AI mode is not a substitute for review.** Always preview the variation on a real page (and ideally a real preview link) before publishing. ## Next steps * **[Manual mode](/app/visual/manual-mode)** — fine-tune accepted changes. * **[Preview and QA](/app/visual/preview-and-qa)** — preview before publishing. # Design to Variant: Import from Figma or Image Source: https://docs.growthbook.io/app/visual/design-import Turn a Figma frame or a mockup image into a testable A/B variation that GrowthBook rebuilds and injects into the live page. Pro **AI Visual Editor** is available on Pro and Enterprise plans. > [!NOTE] > > **Review generated variations** > > Generated results can vary, so always review the variation before you save it. Design → Variant also needs an up-to-date GrowthBook server and a configured vision-capable AI model. If your instance hasn't enabled it yet, the panel will say so and you can fall back to a mockup image (or ask an admin to update the server). Design → Variant turns a **Figma frame** or a **mockup image** into a self-contained, testable variation. Instead of nudging the existing page element by element, you hand the AI a finished design and it rebuilds it as a **scoped component** (HTML + CSS, occasionally a little JS) and places it on the page as a variation. This is the fastest way to test a design your team already mocked up — a new hero, a redesigned pricing card, a promo banner — without hand-coding it. ## When to use it * ✅ A **self-contained section or component**: a hero, a feature card, a callout, a banner, a redesigned form. * ✅ You already have the design in **Figma** or as an **image** (a screenshot, an exported mockup, a design from any tool). * ⚠️ **Not** for full-page redesigns or whole-navigation overhauls. The AI is tuned to produce one in-page component; if the design is essentially a new page, it will tell you it's too large and decline rather than produce something broken. Break it into sections instead. ## Open the importer 1. Install and connect the [AI Visual Editor](/app/visual/install-and-connect) and open the side panel on the page you want to test. 2. In **AI mode**, click **Import from Figma or a mockup**. The **Design → Variant** panel opens. ## 1. Choose a source The panel has two source tabs: ### Figma link Paste a link to a **specific frame** — the URL must include a `node-id` (you get one by right-clicking a frame in Figma and choosing **Copy link to selection**). A link to a whole file without a frame selected won't work. Importing from Figma requires connecting your Figma account once (see [Connecting Figma](#connecting-figma) below). The connection is **read-only** and scoped to the file you link — GrowthBook never edits your Figma files. ### Mockup image No Figma needed. **Upload** a PNG, JPEG, or WebP (up to 8 MB), or **paste an image URL**. Anything works: a Figma export, a screenshot of a competitor, a Sketch/Photoshop mockup, even a whiteboard photo. ## 2. Place it on the page * **Place it in** — click **Pick element** and choose a container on the page (or type a CSS selector). This defaults to the whole page if you don't pick one. * **Placement** — how the generated component is positioned relative to the target: | Mode | Result | | - | - | | **Append inside** | Added as the last child of the target (the default). | | **Insert before** | Added immediately before the target, as its previous sibling. | | **Insert after** | Added immediately after the target, as its next sibling. | | **Replace contents** | Replaces everything inside the target. (Can't target ``/`` — that would wipe the page; pick a specific container.) | ## 3. Add instructions (optional) and generate Use **Extra instructions** to steer the result — e.g. *"use our brand blue for the button"* or *"keep the existing headline copy."* Then click **Generate variation**. The AI builds the component and previews it live on the page. Review it, then **accept** to save it into the variation, or refine your instructions and regenerate. Because the markup and styles are **scoped** to the generated component, they don't leak into or restyle the rest of the host page. > [!TIP] > > **Review before you save** > > The AI reproduces the design as faithfully as it can, but fonts, spacing, and imagery can differ from the source. Treat the output as a strong first draft, eyeball it on the real page, and tweak with [Manual mode](/app/visual/manual-mode) or [global CSS](/app/visual/manual-mode) if needed before saving. ## Connecting Figma The first time you use the Figma source, click **Connect Figma**. This opens Figma's authorization screen in a popup; approve it and you're returned to the side panel. * The integration requests a **single, read-only scope** (`file_content:read`) — enough to render the frame you link and read its layout/colors. It can't modify your files and can't see files you don't link. * The connection is **per-user** — each teammate connects their own Figma account, and tokens are stored encrypted. * You can **Disconnect** at any time from the panel. If you see *"Figma isn't set up on this GrowthBook instance,"* the Figma integration hasn't been configured on your server yet. Use a **mockup image** instead, or ask an admin to set up the Figma OAuth app. (Self-hosted instances need the Figma OAuth credentials configured; GrowthBook Cloud has this managed.) ## Images and placeholders The AI reproduces **layout, typography, spacing, and color** from the design. For photographic imagery it can't recreate, it uses a neutral placeholder (a solid color or gradient from the palette) rather than hot-linking external assets. Swap those for real images afterward with the [image tools](/app/visual/images) — upload, AI-generate, or pick from your library. ## Limitations * **One component per generation.** Design → Variant produces a single scoped component, not a multi-section page. * **Too-large designs are declined.** If the frame is a full page or a large multi-section layout, the AI returns a message explaining it's too large instead of producing a partial result. Target a smaller container. * **Results vary.** Always preview and adjust before saving. * **Needs a vision-capable AI model.** Your org must have a vision-capable model configured in [AI Settings](/integrations/ai) (e.g. Gemini, GPT-4o/5, or Claude). Admins can set the model used for the AI Visual Editor there. ## Next steps * **[AI mode](/app/visual/ai-mode)** — describe changes in natural language for element-level edits. * **[Manual mode](/app/visual/manual-mode)** — fine-tune the generated component with the structured editor. * **[Images](/app/visual/images)** — replace the placeholders with real or AI-generated imagery. * **[Preview and QA](/app/visual/preview-and-qa)** — check the variation before publishing. # A/B Test Images: Upload, AI Generation, Library Source: https://docs.growthbook.io/app/visual/images Replace, upload, or AI-generate images for A/B test variations in GrowthBook. Reuse images across experiments with the shared image library. Pro **AI Visual Editor** is available on Pro and Enterprise plans. Images are some of the highest-impact A/B tests you can run — hero photography, product imagery, illustration style. The [AI Visual Editor](/app/visual) gives you three ways to swap an image in a variation: 1. **Upload** — drop in a file from your machine. 2. **AI generation** — describe the image and let a configured AI model produce it, optionally using the existing image as a reference. 3. **Library** — pick from images your organization has already uploaded or generated, reusable across experiments and projects. All three paths land in the same `` (or ``) replacement and are managed identically in the [Changes panel](/app/visual/manual-mode). ## Replacing an image Open the [AI Visual Editor](/app/visual/install-and-connect) and target the page you want to edit. Select the `` element you want to replace. In Manual mode, the property panel shows an **Image** group with three tabs: **Upload**, **AI**, and **Library**. ### Upload tab 1. Click the upload area (or drag-and-drop a file onto it). 2. The new image appears as a live preview on the page, replacing the original. 3. Click **Use this image** to accept, or **Cancel** to discard. Supported formats are PNG, JPEG, WebP, and GIF. There's no hard size limit, but very large images slow page load — keep production assets under \~500 KB where possible. #### `` and `srcset` handling Modern image markup often wraps an `` in a `` element with multiple `` candidates and a `srcset` attribute for responsive resolution selection. If you just replace the `src`, the browser will keep picking the original from `srcset` and your variation won't show up. The AI Visual Editor handles this automatically: * The `srcset` attribute on the target `` is removed. * Any `` siblings inside a parent `` are removed. * The new image is the only candidate the browser sees. Reverting the image change (from the Changes panel) restores all of this — `srcset`, `` siblings, original `src` — to the pre-variation state. ### AI tab Type a description of the image you want and click **Generate**. The configured AI model returns one or more candidates that preview live on the page. Common useful prompt patterns: * `Photograph of a person using a laptop in a sunlit coffee shop, candid composition` * `Flat illustration of a chart trending upward, blue and orange palette` * `Product hero of a stainless steel water bottle on a white background, soft shadows` A few options: * **Use current image as reference** — appears when the selected model supports it. The AI uses the existing image as a visual cue for composition/style. Useful for "keep the same scene but with a different background color" type requests. * **Model picker** — appears when your org has more than one image model configured. See [available models](#available-image-models) below. * **Aspect ratio and size** — pulled from the original image so the replacement fits the existing layout. (You can still override by editing CSS dimensions in the Background & Border group.) When you're happy with a candidate, click **Use this image** to accept it into the variation. Generated images are stored in the [image library](#library-tab) so you can reuse them later. ### Library tab The Library tab shows every image your organization has uploaded or accepted from AI generation, across all experiments and projects. * **Grid view** of thumbnails with newest first. * **Click a thumbnail** to preview it as the new image; click **Use this image** to accept. * **Refresh icon** at the top to re-fetch the library — useful if a teammate just uploaded an image. * **Image count** in the header. Library images thumbnail-load lazily, so the panel stays responsive even with hundreds of images. #### Quarantine indicator AI-generated images that haven't been accepted into a variation yet show a small **orange dot** badge. These are temporary — they live in a quarantine bucket and the storage lifecycle removes them after 7 days. A short legend at the bottom of the library explains the dot. Why quarantine? Image generation produces a lot of throwaway candidates. We don't want every "let me try one more prompt" attempt to clutter your library forever. Once you accept a candidate into a variation, it gets promoted to permanent storage and the orange dot goes away. ## Available image models The model used for AI generation is set by your org admin in [AI Settings](/integrations/ai). The current options are: | Provider | Model | Reference image | | - | - | - | | Google | Gemini 2.5 Flash Image (nano-banana) | Yes | | Google | Gemini 3 Pro Image (preview) | Yes | | Google | Imagen 4 Fast | No | | Google | Imagen 4 | No | | Google | Imagen 4 Ultra | No | | OpenAI | DALL-E 3 | No | | OpenAI | GPT Image 1 | No | | OpenAI | GPT Image 1 Mini | No | | xAI | Grok 2 Image | No | Picking the right model is a tradeoff between cost, latency, and quality: * **Fastest / cheapest**: Imagen 4 Fast, GPT Image 1 Mini. * **Best general quality**: Imagen 4 Ultra, GPT Image 1, Gemini 3 Pro. * **Need reference image**: any Gemini model. If your org has multiple image models configured, the model picker appears in the AI tab. ## Reverting an image change To restore the original image: 1. Open the **Changes** panel at the bottom of the side panel. 2. Find the row corresponding to the image change. 3. Click **Revert**. Revert clears the image swap *and* any `srcset` / ` > source[srcset]` removals the editor added when applying it. The element returns to exactly its pre-variation state. ## Storage and privacy Uploaded and AI-generated images are stored in GrowthBook's image storage bucket, namespaced by your organization ID. They're served from a CDN-fronted URL so SDKs can fetch them efficiently when delivering variations. A few specifics: * **Permanent storage** (accepted into a variation) lives at `/visual-editor/`. * **Quarantine storage** (AI candidates not yet accepted) lives at `gen//visual-editor/` and is auto-cleared after 7 days. * **Cross-org isolation** — the library API only returns objects under your org's prefix. Other organizations' images are never visible. * **Image URLs are public** in the sense that anyone with the URL can fetch the image. Don't AI-generate images containing sensitive information. For AI generation specifically, the prompt text and (if used) the reference image are sent to the configured AI provider — see [AI Integrations](/integrations/ai) for what each provider does with that data. ## Image-based experiment ideas A few common patterns that move metrics: * **Hero photography style**: studio vs. lifestyle, person vs. product. * **Product framing**: white background vs. in-use, single product vs. context. * **Illustration vs. photography** for explainer sections. * **Color palette match**: does an image that matches your brand palette outperform a contrasting one? * **Faces vs. objects**: images with human faces often drive higher engagement, but the effect is category-specific. Always pair an image change with the right success metric (CTR, conversion, time-on-page) — image impact varies enormously by industry and audience. ## Next steps * **[AI mode](/app/visual/ai-mode)** — generate images as part of a larger AI-driven variation. * **[Manual mode](/app/visual/manual-mode)** — full reference for the rest of the structured editor. * **[Preview and QA](/app/visual/preview-and-qa)** — preview the variation before publishing. * **[AI Integrations](/integrations/ai)** — provider setup and the full AI feature list. # Install and Connect the GrowthBook AI Visual Editor Source: https://docs.growthbook.io/app/visual/install-and-connect Install the GrowthBook AI Visual Editor from the Chrome Web Store and connect it to your GrowthBook account with a single click. Pro **AI Visual Editor** is available on Pro and Enterprise plans. The GrowthBook AI Visual Editor is a standalone Chrome extension. Setup is two clicks and about thirty seconds — install the extension, then click **Connect with GrowthBook**. ## 1. Install the extension Install **[GrowthBook AI Visual Editor](https://chromewebstore.google.com/detail/growthbook-visual-editor/nbomejknbpkcpjdagefhichaajpoempk)** from the Chrome Web Store (Chrome, Edge, Brave, Dia, and most Chromium browsers). ## 2. Open the side panel ![The GrowthBook AI Visual Editor Getting Started](https://mintcdn.com/growthbook-ea15456d/J3C3juKhu0f7KEr_/static/images/visual-editor/ve-connect-step1.png?fit=max\&auto=format\&n=J3C3juKhu0f7KEr_\&q=85\&s=38c7fd903ac45d56239a73aaf190f44a) Open the extension menu (top right) and choose **GrowthBook Visual Editor**. The first time you open the side panel, you'll see a **Connect with GrowthBook** button. ## 3. Click "Connect with GrowthBook" Click **Connect with GrowthBook**. A new tab opens to GrowthBook: ![Connect with GrowthBook button to be signed in](https://mintcdn.com/growthbook-ea15456d/J3C3juKhu0f7KEr_/static/images/visual-editor/ve-connect-step2.png?fit=max\&auto=format\&n=J3C3juKhu0f7KEr_\&q=85\&s=d549527553b5a32502799cdc31351599) 1. **Already signed in, one org** — an AI Visual Editor access key is generated and passed back to the extension automatically. The tab closes; the side panel shows you're connected. 2. **Not signed in** — log in first, then the flow continues as above. 3. **Multiple orgs** — pick which org to connect to from a dropdown, then the key is generated. No copying tokens or pasting strings. ### Where the key is stored The access key is stored in the extension's local storage on your machine. It's not synced across browsers, and it's only sent in the `Authorization` header of requests to the GrowthBook API host the extension is configured for. ### Self-hosted GrowthBook > [!TIP] > > **Self-hosted GrowthBook AI Visual Editor support is coming soon.** The one-click flow works on GrowthBook Cloud and self-hosted instances the extension has synced with before. On a fresh install pointing at self-hosted: 1. Open the gear menu → **API host** and set your host (e.g. `https://api.growthbook.io` or `https://localhost:3100`). 2. Paste a [personal access token](https://app.growthbook.io/account/personal-access-tokens) (or the equivalent on your instance). 3. Click **Connect with GrowthBook**. Still failing? See [Troubleshooting → Extension can't connect](/app/visual/troubleshooting#extension-cant-connect-to-my-self-hosted-growthbook). ## 4. The gear menu Once connected, there's a gear icon in the side panel header. Open it for: * **Status** — shows whether the extension is connected and whether the editor is targeting the active tab's domain. * **Language** — change the side panel's language. Independent of your GrowthBook web UI language. * **Dark mode** — toggles the side panel's UI theme. Independent of your GrowthBook web UI theme. * **Change Connection** — change which GrowthBook host you're talking to (e.g., switch from production to staging, or set a self-hosted host) or change the personal access token. * **Disconnect** — clear the stored key and return to the disconnected state. Use this when handing off the browser to someone else. ## Next steps * **[AI mode](/app/visual/ai-mode)** — write your first variation. * **[Running on your site](/app/visual/running-on-your-site)** — install the SDK so variations render in production. # Manual Mode: Point-and-Click A/B Test Editor Source: https://docs.growthbook.io/app/visual/manual-mode Edit page content, typography, background, spacing, and visibility with GrowthBook's point-and-click AI Visual Editor controls. Pro **AI Visual Editor** is available on Pro and Enterprise plans. Manual mode is the point-and-click editor inside the [AI Visual Editor side panel](/app/visual). Click an element on your page, and you get structured controls for everything you can change on it — content, typography, background and border, spacing, and visibility — plus an escape hatch for arbitrary CSS and JavaScript. Manual mode is the right choice when: * You know exactly what you want to change and you want pixel-level control. * You're fine-tuning a variation that started in [AI mode](/app/visual/ai-mode). * The change is too narrow to be worth a prompt (e.g., "make this padding 12px"). * You want to copy a specific style from a design spec. ## Picking an element Switch to the **Manual** tab in the side panel. With the panel open, hover any element on your page — it highlights with a blue overlay. Click to select it. A sticky bar at the top of the side panel shows what's currently selected (its tag, classes, and a short selector path). You can: * **Pick a different element** — click another element on the page, or click the picker icon in the sticky bar. * **Navigate nearby** — use the "tree" icon to navigate to nearby elements. See below * **Deselect** — click the x on the right side of the selected bar or pick another element to deselect the current one. ### Detailed Selector ![The GrowthBook AI Visual Editor detailed selector](https://mintcdn.com/growthbook-ea15456d/J3C3juKhu0f7KEr_/static/images/visual-editor/ve-detailed-selector.png?fit=max\&auto=format\&n=J3C3juKhu0f7KEr_\&q=85\&s=62c88508ba1fa993661c826ddfc49044) Sometimes clicking on an element selects a parent or child instead of the one you want. In that case, use the sticky bar's selector path to select the exact element. The path shows the current element and its parent elements, sibling elements, and children. Hover over the path to see it selected on the page. ## Property groups Once you've selected an element, the panel fills with collapsible property groups. Each group corresponds to a category of change. ### Content ![The GrowthBook AI Visual Editor HTML options](https://mintcdn.com/growthbook-ea15456d/66H-dO8hZfIFVUdN/static/images/visual-editor/ve-text-editor.png?fit=max\&auto=format\&n=66H-dO8hZfIFVUdN\&q=85\&s=ed788a49fc46f43f51b3a1aaa3e01c91) ![The GrowthBook AI Visual Editor HTML editor](https://mintcdn.com/growthbook-ea15456d/66H-dO8hZfIFVUdN/static/images/visual-editor/ve-text-editor-html.png?fit=max\&auto=format\&n=66H-dO8hZfIFVUdN\&q=85\&s=672291fb4f8b6f71d929ef53210e9544) Edit what the element actually says and links to: * **Inner HTML** — change the visible text or markup inside the element. Multiline-supported. If the selected element has HTML inside of it, you'll be presented with two modes for editing content: edit as text and flatten the HTML, or edit as HTML. ### Links ![The GrowthBook AI Visual Editor Link Editor](https://mintcdn.com/growthbook-ea15456d/J3C3juKhu0f7KEr_/static/images/visual-editor/ve-link-editor.png?fit=max\&auto=format\&n=J3C3juKhu0f7KEr_\&q=85\&s=2e54e077ebf93628accff31af7a42026) If the selected element is a link (``), you'll get additional controls for editing the URL and link behavior. It will let you set the `href`, and the option to open in a new tab (`target="_blank"`). If the link opens in a new tab, it will add `rel="noopener noreferrer"` attribute for security. ### Classes ![The GrowthBook AI Visual Editor Class Editor](https://mintcdn.com/growthbook-ea15456d/J3C3juKhu0f7KEr_/static/images/visual-editor/ve-class-editor.png?fit=max\&auto=format\&n=J3C3juKhu0f7KEr_\&q=85\&s=b2238fd2f2300e915abea3202698b624) The **Classes** field shows the element's current classes and allows you to add or remove classes. This is a quick way to apply existing styles from your CSS without needing to copy individual properties. ### Typography ![The GrowthBook AI Visual Editor Typography editor](https://mintcdn.com/growthbook-ea15456d/66H-dO8hZfIFVUdN/static/images/visual-editor/ve-typeography.png?fit=max\&auto=format\&n=66H-dO8hZfIFVUdN\&q=85\&s=f05619ffe44ae640ae40a26f05f4ac06) Controls for text styling: * **Font size** — with a unit selector (`px`, `em`, `rem`, `%`) * **Font weight** — preset weights and custom numeric input * **Color** — color picker plus hex input * **Alignment** — left / center / right / justify (segmented control) * **Line height** — unitless, `px`, `em`, `rem`, or `%`. Empty unit (`-`) means unitless, which is the CSS recommendation for line-height. * **Letter spacing** — `px`, `em` * **Text decoration** — none / underline / strikethrough / overline Alignment and text decoration share a single row to keep the panel compact. ### Background & Border ![The GrowthBook AI Visual Editor Background and Border](https://mintcdn.com/growthbook-ea15456d/J3C3juKhu0f7KEr_/static/images/visual-editor/ve-background-borders.png?fit=max\&auto=format\&n=J3C3juKhu0f7KEr_\&q=85\&s=f695d685c53b7aa5eafa4ea49d76733a) For containers, cards, and any visually styled element: * **Background color** — color picker with a clear option * **Border** — width, style (solid / dashed / dotted / none), and color. The color swatch is positioned so the native color picker doesn't render off-screen for elements near the right edge of the viewport. * **Box shadow** — None / Small / Medium / Large presets. The presets follow common design-system scales; for custom shadows, use Global CSS. * **Background image** — paste a URL or use one of the [image flows](/app/visual/images). Hidden on `` elements (use Content → image replace instead). #### Background Images ![The GrowthBook AI Visual Editor Background Images](https://mintcdn.com/growthbook-ea15456d/J3C3juKhu0f7KEr_/static/images/visual-editor/ve-background-image.png?fit=max\&auto=format\&n=J3C3juKhu0f7KEr_\&q=85\&s=7d88a8c63d3a6c6a3ddfcf75e7d2578e) The background image area allows you to set a background image on any element. This is similar to the `` replacement, but has a few more options for fitting that background images have. ### Layout ![The GrowthBook AI Visual Editor Layout](https://mintcdn.com/growthbook-ea15456d/J3C3juKhu0f7KEr_/static/images/visual-editor/ve-layout.png?fit=max\&auto=format\&n=J3C3juKhu0f7KEr_\&q=85\&s=7b025dd9ed27b8e9f3bd584bb193d7f6) The Layout group includes controls for choosing the display mode and adjusting spacing with padding and margin editors. * **Display type** — choose between `block`, `inline-block`, `inline`, `flex`, `grid`, and `none`. Setting to `none` is a quick way to hide an element without removing it from the DOM. * **spacing** — click and drag horizontally over a value to scrub it up or down (similar to Figma) for padding and margin. * **Width & Height** — Select the width, max-width, height and max-height for your element. With unit selectors. Setting to `auto` removes the mutation. Manual mode writes per-side longhands (`padding-top`, `margin-right`, etc.) and clears any shorthand `padding`/`margin` declaration, so the CSS cascade behaves predictably. ### Visibility * **Hide this element** — a quick toggle that sets `display: none`. * **Opacity** — slider from 0% to 100% plus a number input. Setting to 100% removes the mutation (no point keeping `opacity: 1` on the element). ## Global CSS and Custom JavaScript For changes that don't fit cleanly into a single element's property groups, use the top-level **CSS** and **JS** tabs: ### Global CSS Inject a CSS block that runs alongside the variation. Useful for: * Page-wide background color or font changes * Hover states that the structured controls can't express * Animations * `@media` queries for responsive tweaks (the only way to scope changes per breakpoint today) The CSS is inserted into a `