Client-side bootstrapping

Contents

Note: Bootstrapping feature flags is available in our JavaScript web, React Native, iOS, Android, and Flutter SDKs. Mobile SDKs persist identity on-device, so anonymous bootstrap seeding applies only on the first launch. An identified bootstrap can also reconcile identity on later launches. See behavior on mobile SDKs.

Since there is a delay between initializing PostHog and fetching feature flags, feature flags are not always available immediately. This makes them unusable if you want to do something like redirecting a user to a different page based on a feature flag.

To have your feature flags available immediately, you can initialize PostHog with precomputed values until it has had a chance to fetch them. This is called bootstrapping. After the SDK fetches feature flags from PostHog, it will use those flag values instead of bootstrapped ones.

To bootstrap PostHog with feature flags, use the bootstrap key in the initialization config and add feature flag values to it:

posthog.init('<ph_project_token>', {
api_host: 'https://us.i.posthog.com',
defaults: '2026-05-30',
bootstrap: {
featureFlags: {
'flag-1': true,
'variant-flag': 'control',
'other-flag': false,
},
},
})

Setting the correct flag values

To ensure you are bootstrapping PostHog with the correct flag values, we recommend fetching the flags values from your server alongside the page load request, and then passing them to your frontend.

You can do this by:

  1. When receiving a frontend request, fetch all the feature flags for the user on the backend by calling getAllFlags().
  2. Return the frontend request with the flags values in the response.
  3. On the frontend, get the flag values from the response and add them to the bootstrap key in the PostHog initialization.

Tip: to ensure your request is as quick as possible, use local evaluation on your server.

Bootstrapping in single-page apps

In environments where posthog.init only runs once during a session (for example, SPAs like Next.js using instrumentation-client), you have two options:

  • Server-side pre-evaluation: Evaluate flags before the app renders, then pass the values into your app and add them to bootstrap or use them directly.
  • Client-side pre-evaluation: Evaluate the flag in an earlier page/state than the one where you need the value, then store and reuse it immediately when required.

Both approaches avoid flicker and achieve the same outcome as bootstrapping, as long as you use the same distinct_id across client and server.

Bootstrapping with a distinct ID

The bootstrap object accepts identity, session, and feature flag values. The JavaScript web SDK uses distinctID and isIdentifiedID. Other SDKs use the casing shown in the examples below.

  • distinctID sets the distinct ID during PostHog's initialization. Include the same distinct ID you used to call getAllFlags() on your server so server-side and client-side flag evaluations use the same identity.

  • isIdentifiedID tells the SDK whether distinctID belongs to an identified person or an anonymous visitor. Set it to true for a signed-in ID, such as a database ID. A previous $identify event isn't required. Set it to false, or omit it, when supplying a generated anonymous ID.

When isIdentifiedID is true, the JavaScript web, iOS, Android, and Flutter SDKs reconcile the bootstrapped ID with any persisted identity:

  • No persisted identity – The SDK uses the bootstrapped ID and marks it as identified without emitting $identify.
  • An anonymous identity exists – If the IDs match, the SDK marks the existing identity as identified without emitting $identify. If they differ, the SDK calls identify() automatically to link the anonymous activity to the bootstrapped ID. This emits $identify when capturing is enabled; while opted out, only the local identity changes.
  • The same identified identity exists – The SDK keeps it without emitting a redundant $identify.
  • A different identified identity exists – The SDK preserves the existing identity and emits a warning. Call reset() before initialization when you intend to switch users.

The automatic merge follows your person profile settings. If person profiles are set to never, the SDK preserves a different anonymous identity instead of calling identify().

Use bootstrap when your app knows the signed-in identity before SDK initialization. If your app loads the identity asynchronously, call identify() when it becomes available instead.

  • sessionID connects sessions across domains so you get an accurate session count and complete session replays. To get the session ID, call posthog.get_session_id().

  • featureFlags makes flag values available as soon as PostHog loads. Call posthog.getAllFlags() (or the equivalent method) on your backend and pass the values to your frontend in the bootstrap object.

posthog.init('<ph_project_token>', {
api_host: 'https://us.i.posthog.com',
defaults: '2026-05-30',
bootstrap: {
distinctID: 'distinct_id_of_your_user',
isIdentifiedID: true,
sessionID: 'session_id_of_user_session',
featureFlags: {
'flag-1': true,
'variant-flag': 'control',
'other-flag': false,
},
},
})

Behavior on mobile SDKs

The iOS, Android, and Flutter SDKs bootstrap through PostHogBootstrapConfig and persist identity and flags on the device, so bootstrapping behaves slightly differently than on the web. Assign config.bootstrap = PostHogBootstrapConfig(...) before calling setup(). Bootstrapping requires iOS SDK 3.66.0+, Android SDK 3.55.0+, or Flutter SDK 5.31.0+.

React Native is also a mobile SDK, but it uses the posthog-js-style bootstrap option shown in the examples above (not PostHogBootstrapConfig) and follows the standard behavior.

  • Bootstrapped identity applies during setup. On a fresh install, setting it before setup() means events captured synchronously during initialization (like Application Installed) carry your distinct ID instead of the SDK-generated UUID.
    • An anonymous bootstrap (isIdentifiedId: false, the default) seeds the anonymous ID only when none is persisted yet. Once an anonymous ID exists on disk, or the person has been identified, the SDK ignores it.
    • An identified bootstrap (isIdentifiedId: true) is for a signed-in identity available to your app (for example, from a backend session token). On a fresh install, it seeds the distinct ID, marks the person identified, and generates a separate device ID. On a returning install, a matching anonymous ID is marked identified without emitting $identify; a different anonymous ID is merged via identify() when person profiles are enabled. This emits $identify unless capturing is opted out. A different, already-identified person is left untouched.
  • Bootstrapped flags are served until the first /flags response, then replaced. A complete /flags response takes over entirely, so bootstrapped-only keys don't persist past it. Only enabled flags are seeded: a true boolean or a non-empty variant string. A false or empty value is dropped, matching posthog-js. Seed payloads with the separate featureFlagPayloads option. Flag values and payloads must be JSON-serializable, or they're dropped. Bootstrapped flags are cleared on reset().

The feature-flags-loaded signal fires as soon as bootstrapped flags are applied, so startup logic can read them immediately. These SDKs don't support the sessionID bootstrap option. When person profiles are set to never, the SDK preserves a different anonymous identity instead of merging it into an identified bootstrap.

Flutter web: bootstrap is not applied on Flutter web because the web SDK hooks onto an already-initialized posthog-js instance. Configure bootstrap in your posthog.init({...}) call instead.

Overriding feature flags

Bootstrapped feature flag values are temporary and are disregarded after PostHog fetches flag values. If you are trying to override feature flag values in a persistent manner, some PostHog SDKs support overriding flags:

posthog.overrideFeatureFlags({ 'my-feature-flag': true })

Examples

Community questions

Was this page useful?