Skip to main content

Setting Up a Growthbook Experiment

Feature Flag Typesโ€‹

When creating a new feature flag or experiment, add the new type to the growthbook.d.ts file with its respective type. See more about feature types here.

General rule of thumb: if the experiment is A/B (two variants; one control, one experiment), use boolean. If testing multiple variants (A/B/C), use a union of the variant letters. In the AppFeatures map, strictly use the values being set instead of a generic string:

export type AppFeatures = {
'cart_[xxx-xxx-xxx]_2026-01': boolean;
'pdp_[xxx-xxx-xxx-]_2026_01': 'A' | 'B' | 'C';
'lp_[xxx-xxx-xxx]_2026_01': 'A' | 'B' | 'C';
// ...
};

Server-side: getGrowthbookFeatureโ€‹

This server-side helper wraps GB's getFeatureValue function (see here), giving us a typed interface to read feature flag values in loaders.

// app/lib/growthbook/helper.server.ts

export const getGrowthbookFeature = <FeatureType extends keyof AppFeatures>(
growthbook: GrowthBook,
featureKey: FeatureType,
defaultValue: AppFeatures[FeatureType]
) => growthbook.getFeatureValue(featureKey, defaultValue);

How it's used in a loader:

// products.$handle.tsx

const enableColorSelectorPlacement = getGrowthbookFeature(
args.context.growthbook,
'pdp_[xxx-xxx-xxx]_2026_01',
'A'
);

return {
// other loader variables
enableColorSelectorPlacement,
};

Client-sideโ€‹

Passing the flag to componentsโ€‹

After the feature flag value is brought in via useLoaderData, pass it down to the relevant component. Depending on the experiment's scope, this may be directly into a provider (for broad access across a page) or into a more isolated component.

// products.$handle.tsx

<PdpProvider
enableColorSelectorPlacement={enableColorSelectorPlacement}
>
{children}
</PdpProvider>

// Example of the variable being used in a smaller reusable component:
<div
dangerouslySetInnerHTML={{ __html: description }}
className={clsx(enableColorSelectorPlacement === 'C' && 'hidden')}
/>

Analyticsโ€‹

Add useSendGBAnalyticsEvent at the top level of the page component so the variant assignment is tracked on mount. When a client navigates away, GB persists that value so they get a consistent experience on return.

// products.$handle.tsx

const ProductDetailPage: FC = () => {
useSendGBAnalyticsEvent('pdp_[xxx-xxx-xxx]_2026_01');
// ...
};

Conditional analyticsโ€‹

Some experiments require the analytics hook to only run in specific contexts (e.g. a subset of pages). Since conditionally calling a hook directly violates the rules of hooks, use the ConditionalSiteAnalytics component instead:

// app/lib/experiments/helper.tsx

export const ConditionalSiteAnalytics = ({ feature }: { feature: keyof AppFeatures }) => {
useSendGBAnalyticsEvent(feature);
return null;
};
// app/components/PageLayout.tsx

export function PageLayout({ props }) {
return (
<main>
{isSwatchLP && (
<ConditionalSiteAnalytics feature={'lp_hide-announcement-site-switcher_2026_01' as keyof AppFeatures} />
)}
</main>
);
}

You can verify events are firing by opening DevTools and logging window.dataLayer โ€” events should have growthbook prepended with the experiment ID/name.