Monday, July 14, 2025
A/B Testing Onboarding Flows with OnboardJS and PostHog


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."
- Go to your PostHog dashboard, navigate to Experiments, and click New experiment.
- 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).
- Name:
- 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.
- 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.
- 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:
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
, andonboarding_flow_completed
. - Enrich these events with the correct experiment variant (
control
orwith-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.
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.
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?
- 🚀 Explore the Live Demo to see the A/B test in action.
- 💻 Clone the Example Project on GitHub to get your hands on the code.
- ⭐ Star OnboardJS on GitHub to support the project and stay updated.
- 💬 Join our Discord Community to ask questions and share your own A/B testing ideas.
Next up, how to A/B test different onboarding flows with OnboardJS. Coming soon! I will link it right here too!