Skip to content

fix(browser): Accept precisely-typed GrowthBook class in growthbookIntegration#21825

Open
codr wants to merge 1 commit into
getsentry:developfrom
codr:fix/growthbook-integration-class-type
Open

fix(browser): Accept precisely-typed GrowthBook class in growthbookIntegration#21825
codr wants to merge 1 commit into
getsentry:developfrom
codr:fix/growthbook-integration-class-type

Conversation

@codr

@codr codr commented Jun 27, 2026

Copy link
Copy Markdown

Problem

Following the integration's own documented usage produces a TypeScript compile error. Copy-pasting the snippet from the growthbookIntegration JSDoc / docs:

import { GrowthBook } from '@growthbook/growthbook';
import * as Sentry from '@sentry/browser'; // or @sentry/react

Sentry.init({
  dsn: '...',
  integrations: [Sentry.growthbookIntegration({ growthbookClass: GrowthBook })],
});

fails to type-check against the real @growthbook/growthbook class:

error TS2322: Type 'typeof GrowthBook' is not assignable to type 'GrowthBookClass'.
  Type 'new <AppFeatures...>(options?: Options) => GrowthBook<AppFeatures>'
    is not assignable to type 'new (...args: unknown[]) => GrowthBook'.

So a consumer who does exactly what the docs say is forced to add a cast — e.g. growthbookClass: GrowthBook as unknown as ... — which is poor DX and directly contradicts the documented example. Reproduces on the latest release (10.62.0) via both @sentry/browser and @sentry/react.

Root cause

The parameter is typed as a constructor with unknown[] args:

export type GrowthBookClass = new (...args: unknown[]) => GrowthBook;        // @sentry/browser
export type GrowthBookClassLike = new (...args: unknown[]) => GrowthBookLike; // @sentry/core

GrowthBook's real constructor is constructor(options?: Options). A constructor with a narrow parameter is not assignable to one declared with (...args: unknown[]) — constructor parameters are contravariant, and unknown is not assignable to Options — so typeof GrowthBook is rejected.

This only surfaces through @sentry/browser / @sentry/react. @sentry/core annotates its export as IntegrationFn ((...rest: any[])), which erases the parameter type and hides the problem; @sentry/browser re-declares the wrapper with the strict { growthbookClass: GrowthBookClass } parameter (via satisfies IntegrationFn), preserving the strict type that the public API exposes.

Fix

The integration never constructs growthbookClass — it only reads growthbookClass.prototype to monkey-patch isOn / getFeatureValue. Typing the parameter as exactly that:

export type GrowthBookClass = { prototype: GrowthBook };
export type GrowthBookClassLike = { prototype: GrowthBookLike };

accepts any class whose instances expose the wrapped surface (so the documented typeof GrowthBook usage works with no cast), still rejects classes missing isOn / getFeatureValue, and avoids any. It also makes the internal growthbookClass.prototype as GrowthBookLike cast redundant, which is removed.

Test

Adds packages/browser/test/integrations/featureFlags/growthbook/integration.test.ts: passes a mock class with a real, narrow constructor to growthbookIntegration with no cast (the type regression guard) and asserts boolean evaluations are captured to the scope's flag context.


  • If you've added code that should be tested, please add tests.
  • Ensure your code lints and the test suite passes (yarn lint) & (yarn test).
  • No related issue is linked — happy to file one if preferred.

…tegration

`growthbookIntegration({ growthbookClass: GrowthBook })` — the call shown in the
integration's own docs — did not type-check against the real
`@growthbook/growthbook` class, forcing consumers to cast. The parameter was
typed as `new (...args: unknown[]) => GrowthBook`, and a narrow constructor
(`constructor(options?: Options)`) is not assignable to an `unknown[]`
constructor.

The integration only reads `growthbookClass.prototype`, so type the parameter
structurally as `{ prototype: GrowthBook }`. This accepts a real GrowthBook
class with no cast, still rejects classes missing `isOn`/`getFeatureValue`, uses
no `any`, and makes the internal prototype cast redundant.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@codr codr requested a review from a team as a code owner June 27, 2026 03:58
@codr codr requested review from Lms24, logaretm and mydea and removed request for a team June 27, 2026 03:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant