Monday, July 14, 2025

A/B Testing Onboarding Flows with OnboardJS and PostHog

Soma Somorjai
OnboardJS with Posthog for A/B testing onboarding flows

What if you could increase your user activation rate by 20% this month? For most products, the onboarding flow is a "black box"—users go in, and some percentage come out the other side, but we rarely know exactly where they drop off or why.

Traditional A/B testing often fails here. Onboarding isn't a static landing page; it's a stateful, conditional, multi-step journey. You need a toolset designed for this complexity.

Today, I'm going to show you how a simple, easily understandable but powerful, data-driven optimization loop might look like for you user onboardig flow. We'll combine the headless, stateful power of OnboardJS with the robust experimentation and analytics of PostHog. By the end of this guide, you'll have a practical, reusable system for continuously improving your product's first impression.

We'll be working directly with our live Next.js A/B testing demo, and you can find all the code in our open-source examples repository.

All I ask in return is a ⭐ on GitHub 😉!

Step 1: Setting Up the Experiment in PostHog

First, we need to define our experiment in PostHog. Our hypothesis for this example is: "Displaying a motivational progress indicator will increase the onboarding flow completion rate."

  1. Go to your PostHog dashboard, navigate to Experiments, and click New experiment.
  2. Name your experiment.
    • Name: Motivational Progress Indicator
    • Feature flag key: motivational-progress-indicator (This key is crucial, as we'll use it in our code).
  3. Define your variants. We'll have two:
    • control: The default experience with no progress bar.
    • with-progress: The variant that displays the progress indicator.
    • Set the rollout to 50% for each variant.
  4. Set your Goal Metric. This is how PostHog determines a winner.
    • Click Add metric and choose Funnel.
    • Step 1: Event onboarding_flow_started.
    • Step 2: Event onboarding_flow_completed.
    • This funnel directly measures our primary success metric: the percentage of users who start the flow and successfully finish it.
  5. Save and launch the experiment.

With our experiment live in PostHog, we're ready to connect it to our Next.js application.

Step 2: Connecting PostHog to OnboardJS

The magic happens by using the official @onboardjs/posthog-plugin. This plugin acts as a bridge, automatically sending lifecycle events from OnboardJS to PostHog and fetching feature flag data.Here’s how we set it up in our OnboardingWrapper.tsx component:

tsx
49 lines
1// src/components/onboarding/onboarding-wrapper.tsx
2
3"use client";
4
5import { OnboardingStep } from "@onboardjs/core";
6import { OnboardingProvider } from "@onboardjs/react";
7import { PropsWithChildren } from "react";
8import FirstStep from "./first-step";
9import PersonaStep from "./persona-step";
10import { ProjectSetupStep } from "./project-setup-step";
11import { createPostHogPlugin } from "@onboardjs/posthog-plugin";
12import posthog from "posthog-js";
13
14// 1. Define the steps in our onboarding flow
15const steps: OnboardingStep[] = [
16  { id: "first-step" },
17  { id: "persona-step", nextStep: "project-setup-step" },
18  { id: "project-setup-step" },
19];
20
21// 2. Map step IDs to React components
22const componentRegistry = {
23  "first-step": FirstStep,
24  "persona-step": PersonaStep,
25  "project-setup-step": ProjectSetupStep,
26};
27
28// 3. Initialize the PostHog plugin
29const posthogPlugin = createPostHogPlugin({
30  // Pass your initialized PostHog instance
31  posthogInstance: posthog,
32  // Tell the plugin which feature flags to be aware of for experiments
33  experimentFlags: ["motivational-progress-indicator"],
34  // Enable automatic tracking of experiment events
35  enableExperimentTracking: true,
36});
37
38export default function OnboardingWrapper({ children }: PropsWithChildren) {
39  return (
40    // 4. Pass the plugin to the OnboardingProvider
41    <OnboardingProvider
42      steps={steps}
43      componentRegistry={componentRegistry}
44      plugins={[posthogPlugin]}
45    >
46      {children}
47    </OnboardingProvider>
48  );
49}

This setup is incredibly powerful. By simply adding the plugin, OnboardJS will now:

  • Automatically capture events like onboarding_flow_started, onboarding_step_active, and onboarding_flow_completed.
  • Enrich these events with the correct experiment variant (control or with-progress) for every user.

Step 3: Implementing the A/B Test in the UI

Now, let's implement the visual change for our experiment. We'll create a component that only renders if the user is in the with-progress variant.

This is where the posthog.getFeatureFlag() function comes in.

tsx
43 lines
1// src/components/onboarding/onboarding-progress.tsx
2
3"use client";
4
5import { useOnboarding } from "@onboardjs/react";
6import { useEffect, useState } from "react";
7import { Progress } from "../ui/progress";
8import posthog from "posthog-js";
9
10const motivationalCopy = [
11  "Let’s get started! 🚀",
12  "You’re making great progress! 💪",
13  "Almost there! 🎯",
14  "All set! 🎉",
15];
16
17export default function OnboardingProgress() {
18  const { state } = useOnboarding();
19  
20  // 1. Get the current variant for this user from PostHog
21  const withProgress =
22    posthog.getFeatureFlag("motivational-progress-indicator") ===
23    "with-progress";
24
25  const [progress, setProgress] = useState(0);
26
27  useEffect(() => {
28    // ... logic to calculate progress percentage
29  }, [state]);
30
31  // 2. If the user is in the 'control' group, render nothing!
32  if (!withProgress) {
33    return null;
34  }
35
36  // 3. If the user is in the 'with-progress' group, render the component
37  return (
38    <div className="mb-6 w-full">
39      {/* ... JSX for the progress bar and motivational text */}
40      <Progress value={progress} />
41    </div>
42  );
43}

It's that simple. The OnboardingProgress component now dynamically appears or disappears based on the experiment variant assigned to the user by PostHog.

Developer Tip: Testing variants locally can be a pain. That's why our example includes an override component. It uses posthog.featureFlags.overrideFeatureFlags() to let you manually switch between variants in your development environment. You can see it in action on the live demo.

tsx
14 lines
1// src/components/experiment-override.tsx
2
3// ...
4<Button
5  onClick={() => {
6    posthog.featureFlags.overrideFeatureFlags({
7      flags: { "motivational-progress-indicator": overrideValue },
8    });
9    router.refresh();
10  }}
11>
12  Override Experiment ({overrideValue})
13</Button>
14// ...

Step 4: Analyzing Results and Making Decisions

After letting your experiment run and gather enough data, head back to the Experiments tab in PostHog. You'll see a dashboard that looks something like this:

  • Conversion Rate: The percentage of users who completed your goal funnel for each variant.
  • Statistical Significance: A confidence score indicating whether the observed difference is real or just due to random chance. Wait for this to reach at least 95% before making a decision.
  • Participants: The number of users in each variant.

If your with-progress variant shows a statistically significant increase in the completion rate, you have a winner! You can then roll out the feature to 100% of users. If not, you've still learned something valuable, and you can archive the experiment and move on to your next hypothesis.

Conclusion: Build, Measure, Learn, Repeat

You've now built a complete, end-to-end system for data-driven onboarding optimization. This isn't a one-off task; it's a continuous loop that empowers both developers and product managers to build better products.With OnboardJS handling the complex state and flow logic and PostHog managing the experimentation and analytics, your team can focus on what truly matters: creating an exceptional first experience for your users.

Ready to get started?

Next up, how to A/B test different onboarding flows with OnboardJS. Coming soon! I will link it right here too!