Skip to main content
RapidDev - Software Development Agency
App Featurespersonalization-ux20 min read

How to Add a Custom Onboarding Flow to Your App — Copy-Paste Prompts Included

A custom onboarding flow needs four pieces: a multi-step form (react-hook-form + Zod), a progress indicator, a Supabase table to store answers, and a completion gate that skips returning users. With Lovable or V0 you can ship a working 5-step onboarding in 4–8 hours. Running cost is $0–25/mo depending on user volume. The hardest part — translating onboarding answers into a personalised dashboard — always needs at least one follow-up prompt.

4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members

Feature spec

Intermediate

Category

personalization-ux

Build with AI

4–8 hours with Lovable

Custom build

1–2 weeks custom dev

Running cost

$0–25/mo up to 1K users

Works on

WebMobile

Everything it takes to ship a Custom Onboarding Flow — parts, prompts, and real costs.

TL;DR

A custom onboarding flow needs four pieces: a multi-step form (react-hook-form + Zod), a progress indicator, a Supabase table to store answers, and a completion gate that skips returning users. With Lovable or V0 you can ship a working 5-step onboarding in 4–8 hours. Running cost is $0–25/mo depending on user volume. The hardest part — translating onboarding answers into a personalised dashboard — always needs at least one follow-up prompt.

What a Custom Onboarding Flow Actually Is

A custom onboarding flow is a multi-step sequence that runs once, immediately after a new user signs up, and uses their answers to personalise the app before they reach the dashboard. A wellness app asks about fitness goals. A project management tool asks about team size and workflow style. An e-commerce app asks about product preferences. The flow must do three things correctly: gate behind authentication, never show twice to returning users, and write its answers into a data model that actually drives the personalised experience — not just store them and forget them. The UI is straightforward; the completion gate and the personalisation writer are where builds go wrong.

What users consider table stakes in 2026

  • Progress indicator showing 'Step 2 of 5' or a percentage bar visible on every step
  • Back button restores the previous step's answers without wiping anything the user typed
  • Skip option on non-critical steps with skipped steps recorded so analytics can track them
  • Returning users bypass onboarding entirely — the gate check happens server-side before the page renders
  • Onboarding answers immediately change the dashboard or feed on completion — not 'we'll personalise your experience soon'
  • Mobile-friendly single-column layout per step with large tap targets
  • Estimated total time shown on the first step so users know what they're committing to

Anatomy of the Feature

Eight components that span form state management, database persistence, server-side gating, and personalisation output. AI tools generate the form UI reliably; the completion gate and personalisation writer need explicit prompting.

Layers:UIDataBackend

Step controller

UI

A React state machine that manages which step is currently active, whether forward navigation is gated by validation, and back navigation history. Implemented with useState (step index) plus Framer Motion AnimatePresence for slide transitions between steps. XState is an option for flows with complex conditional branching, but a simple index is sufficient for linear flows.

Note: Keep step index in React state, not in the URL. If each step is a separate route (/onboarding/step-1, /onboarding/step-2), the browser back button triggers router history instead of the step machine — and users lose unsaved data.

Multi-step form

UI

react-hook-form with a Zod validation schema per step. FormProvider wraps the entire flow — never unmount it between steps. Each step is a separate component that uses useFormContext() to read and write its fields. Validation runs on every step transition; the Next button is disabled if the current step's schema does not pass.

Note: Do not use CSS display:none to hide inactive steps and conditional rendering to show active ones — react-hook-form loses registered field values when components unmount. Wrap all steps in FormProvider from step 1 and keep them mounted, controlling visibility with CSS.

Progress bar

UI

shadcn/ui Progress component or a custom horizontal stepper showing filled circles for completed steps, the active step highlighted, and hollow circles for upcoming steps. Animated transition (Framer Motion layoutId or CSS width transition) between steps gives clear visual feedback that progress is happening.

Note: Show both step number ('Step 2 of 5') and a visual indicator — relying on one alone is less effective for users who are anxious about how long onboarding will take.

User profile onboarding table

Data

Supabase table onboarding_responses stores every answer with user_id as the primary key. Columns are typed to match the input: goal text, experience_level text with a check constraint, preferred_topics text[], notification_frequency text, skipped_steps text[] for tracking skips, and completed_at timestamptz that the completion gate reads. RLS allows only the authenticated user to access their own row.

Note: Use a single row per user with upsert semantics — not one row per step. This simplifies the completion gate query and keeps the schema readable.

Completion gate

Backend

Middleware or a server component that checks onboarding_responses.completed_at for the current user before rendering any authenticated page. Implemented in Next.js as middleware.ts using @supabase/ssr's createServerClient to read the auth cookie server-side — not client-side after hydration. If completed_at is null, redirect to /onboarding. Set a lightweight cookie (onboarding_complete=true) immediately after completion as a fast-path gate.

Note: Do not implement the gate as a client-side check. A flash of the onboarding screen before the session resolves destroys the experience for every returning user.

Personalisation writer

Backend

A Supabase Edge Function or Next.js Server Action that fires on final step submission. Reads onboarding_responses for the user and writes derived preferences to user_settings — dashboard_layout, preferred_topics, notification_frequency — which the dashboard query filters by. This is the bridge between 'we collected data' and 'the app actually feels personalised'.

Note: This component is the one AI tools most frequently skip or simplify. Prompt for it explicitly: 'after onboarding completes, read onboarding_responses and write preferred_topics and dashboard_layout to user_settings'.

Welcome animation

UI

Lottie animation via lottie-react or a Framer Motion confetti burst triggered on the final step confirmation. Plays for 1.5–2 seconds before auto-redirecting to the dashboard. Marks the transition from 'new user' to 'setup complete' with a moment of positive reinforcement.

Note: This is optional but meaningfully increases perceived quality. If you skip it, at minimum show a full-screen success message before the redirect — an instant jump to the dashboard feels like the form broke.

FlutterFlow equivalent

UI

In FlutterFlow, the onboarding flow is a PageView widget where each page corresponds to one onboarding step. App State variables store the step index and the collected answers. On the final page, a Chain of Actions writes the data to a Firebase Firestore onboarding_data document under the user's UID and navigates to the home page.

Note: Conditional step logic — showing step 4 only when step 2 answer equals 'advanced' — requires custom Dart actions in FlutterFlow. Visual condition builders handle simple if/else but cannot express multi-condition branching reliably.

The data model

Two tables cover onboarding collection and user settings. Run this in the Supabase SQL editor after setting up your project:

schema.sql
1create table public.onboarding_responses (
2 user_id uuid references auth.users(id) on delete cascade primary key,
3 goal text,
4 experience_level text check (experience_level in ('beginner', 'intermediate', 'advanced')),
5 preferred_topics text[],
6 notification_frequency text default 'daily',
7 skipped_steps text[] default '{}',
8 completed_at timestamptz,
9 updated_at timestamptz default now()
10);
11
12alter table public.onboarding_responses enable row level security;
13
14create policy "Users can manage own onboarding"
15 on public.onboarding_responses
16 for all
17 using (auth.uid() = user_id)
18 with check (auth.uid() = user_id);
19
20create table public.user_settings (
21 user_id uuid references auth.users(id) on delete cascade primary key,
22 dashboard_layout text default 'default',
23 preferred_topics text[],
24 notification_frequency text default 'daily',
25 onboarding_version int default 1,
26 updated_at timestamptz default now()
27);
28
29alter table public.user_settings enable row level security;
30
31create policy "Users can manage own settings"
32 on public.user_settings
33 for all
34 using (auth.uid() = user_id)
35 with check (auth.uid() = user_id);
36
37create index onboarding_completion_idx
38 on public.onboarding_responses (user_id, completed_at)
39 where completed_at is not null;

Heads up: The partial index on completed_at speeds up the completion gate query — it only scans rows that have actually finished onboarding, which is most rows once the app has been live for a week.

Build it — pick your path

Each tool fits this feature differently. Switch tabs to compare fit, time, the exact steps, and a copy-paste prompt.

Hand-built by developersFit for this feature:

Full control over branching flows, A/B testing, step-level analytics, CRM integration, and re-onboarding campaigns for feature launches.

Step by step

  1. 1Model the onboarding flow as a directed graph: nodes are steps, edges are transitions with conditions; render whichever step the user's current position in the graph points to
  2. 2Integrate a feature flag service (PostHog or LaunchDarkly) to A/B test different step sequences and measure activation rates per variant
  3. 3Fire a PostHog or Mixpanel event on every step transition (onboarding_step_completed, onboarding_step_skipped) to build a funnel analysis identifying drop-off steps
  4. 4Write a CRM integration (HubSpot or Salesforce API call from a Next.js Server Action) that creates a contact with onboarding data as lead qualification properties
  5. 5Build a re-onboarding flow that increments onboarding_version in user_settings — users who completed version 1 are shown a shorter 2-step version 2 flow when a major feature launches

Where this path bites

  • A/B testing onboarding flows requires careful session isolation to prevent users from seeing different variants across devices
  • CRM integration adds significant complexity to the final submission step — consider it a phase-2 addition after the core flow is proven

Third-party services you'll need

The core flow is free. Optional services add animation quality, funnel analytics, and A/B testing:

ServiceWhat it doesFree tierPaid from
SupabaseStores onboarding_responses and user_settings; powers the completion gate query2 projects, 500MB database — sufficient for onboarding data at small scale$25/mo (Pro) at growth stage
LottieFiles (lottie-react)Completion animation on the final onboarding stepFree animations available; open-source player (lottie-react)LottieFiles Pro $20/mo (approx) for premium animation library — optional
PostHogOnboarding funnel analytics: step completion rates, drop-off points, A/B testing flow variants1M events/mo freeScale $450/mo (approx) — optional for early-stage; free tier covers most needs

Swipe the table sideways to see pricing.

What it costs to run

Drag through the tiers to see how your monthly bill scales with users — no surprises later.

Estimated monthly running cost

$0/mo

Supabase free tier easily covers onboarding storage for 100 users. Framer Motion animations are free. No PostHog cost on free tier.

Estimates use verified 2026 pricing and assume typical usage per user. Your real bill depends on activity, storage, and third-party plans.

What breaks when AI tools build this

The failures people actually hit on their first build — the symptom, why it happens, and the exact fix.

Returning users are sent back to onboarding on every login

Symptom: The completion gate checks onboarding_responses.completed_at for the current user, but in Next.js middleware the Supabase session is not available until after the client hydrates. The middleware fires first, sees no session, cannot check the DB, and defaults to redirecting to /onboarding — catching every user on every page load.

Fix: In middleware.ts, use @supabase/ssr's createServerClient to read the Supabase auth cookie directly from the request headers — this works before hydration. Set a lightweight server cookie (onboarding_complete=1) immediately after onboarding finishes and read that cookie in middleware as a fast-path gate, falling back to the DB check only when the cookie is absent.

Back button returns to step 1 instead of the previous step

Symptom: When AI tools generate each onboarding step as a separate route (/onboarding/step-1, /onboarding/step-2 etc.), clicking the browser's back button triggers router history and navigates between routes — which unmounts the current step and resets react-hook-form state. The user loses all answers and lands on step 1.

Fix: Use a single /onboarding route with step index managed entirely in React state (not the router). If deep-linking to a specific step is needed for development, use a query parameter (?step=2) but never rely on it for navigation — state is the source of truth for all transitions.

Form data disappears when navigating back and forward

Symptom: If each step component is conditionally rendered (unmounted when not visible), react-hook-form's register loses the field value on unmount. Navigating from step 3 back to step 2 then forward again shows blank fields — the user has to re-enter their answer.

Fix: Wrap all step components in a persistent FormProvider from the very first step and keep all step components mounted throughout the flow. Use CSS (display: none or visibility: hidden) to hide inactive steps — never unmount them. This ensures react-hook-form's internal store preserves all values across navigations.

The onboarding completion screen flashes briefly on page load for all users

Symptom: The completion gate is an async Supabase query. Before it resolves, the app renders its default state — which is the onboarding flow. Authenticated users who have completed onboarding see the first step flicker for a split second on every page load.

Fix: Show a full-screen loading skeleton while the onboarding status is being fetched. Only render the onboarding step content after the query confirms the user has not completed onboarding. For the fastest gate, use the server-side middleware approach with the completion cookie — this resolves before the page renders, eliminating the flash entirely.

Best practices

1

Keep the total step count to 5 or fewer — each additional step reduces completion rates; move non-essential questions to the user profile page instead

2

Use a persistent FormProvider that wraps all steps and stays mounted throughout the flow — never unmount steps, use CSS to hide inactive ones

3

Implement the completion gate in Next.js middleware using @supabase/ssr so returning users never see the onboarding screen, even for a flash

4

Ask only for what you will use: every onboarding answer must drive a visible personalisation in the dashboard — if you can't point to where an answer is used, remove the step

5

Record skipped steps in skipped_steps[] even when skip is allowed — this data reveals which questions users find irrelevant and is essential for onboarding iteration

6

Show a specific time estimate on the first step ('Takes about 2 minutes') — it meaningfully reduces abandonment for users who are uncertain about the commitment

7

Animate step transitions with a slide-in from the right (forward) and slide-out to the right (back) — this directional motion reinforces the sense of progress

When You Need Custom Development

AI tools handle linear onboarding flows with 3–6 steps reliably. These requirements push beyond what a single prompt session can produce:

  • Onboarding flow has conditional branching — different step sequences for different user types (individual vs. team, enterprise vs. consumer) where the path through the flow depends on earlier answers
  • A/B testing different onboarding versions to measure activation rate — requires a feature flag service, session isolation, and an analytics pipeline to evaluate which variant wins
  • Onboarding is also a sales qualification flow that writes lead data to HubSpot or Salesforce with field mapping to CRM properties
  • Re-onboarding campaign for an existing user base — users who completed version 1 need a shorter update flow when a major feature launches, without disrupting new user onboarding

RapidDev builds these features for production

Real auth, database, and payments — fixed price, delivered in weeks. You own 100% of the code.

Get a fixed-price quote

Frequently asked questions

How do I prevent users from repeating onboarding after they've completed it?

Use a server-side completion gate in Next.js middleware.ts. With @supabase/ssr's createServerClient, you can read the user's auth cookie and query onboarding_responses.completed_at before the page renders. If it's set, allow through. If null, redirect to /onboarding. For speed, also set a lightweight cookie immediately after completion — middleware reads the cookie first and only hits the database when the cookie is absent.

Can I let users go back and change their answers?

Yes — wrap all steps in a persistent react-hook-form FormProvider that never unmounts. Navigate backwards by decrementing the step index in React state; the FormProvider preserves all entered values. If you conditionally render steps (mounting/unmounting), react-hook-form loses the values on unmount and the user sees blank fields on return.

How do I skip optional steps?

Add a Skip button to steps you designate as non-critical. On click, advance the step index without validating the current step's fields, and append the step's ID to the skipped_steps array in your form state. When you save onboarding_responses, write the skipped_steps array to the database so you can analyse which questions users find irrelevant.

Can onboarding steps branch based on earlier answers?

Simple branching (show/hide a single step based on a previous answer) works with conditional rendering of that step component. Complex branching — where the entire sequence of remaining steps changes based on a user segment — requires modelling the flow as a directed graph. This is significantly harder than a linear flow and usually warrants custom development.

How do I track where users drop off in onboarding?

Fire an analytics event (PostHog, Mixpanel, or a Supabase insert) each time a user completes or skips a step. Include the step ID and a timestamp. Build a funnel view showing completion rate per step — a sharp drop between step 2 and step 3 tells you exactly which question is losing people. PostHog's free tier (1M events/mo) is sufficient for most apps at early stage.

Can I personalise the dashboard based on onboarding answers?

Yes, and this is the entire point. After the final step, run a Server Action or Edge Function that reads onboarding_responses and writes to user_settings — preferred_topics, dashboard_layout, notification_frequency. Your dashboard query then filters by user_settings. If you skip this step, you collected data with no effect — which users notice immediately.

How many onboarding steps is too many?

Research consistently shows drop-off increases significantly after step 5. Aim for 3–5 steps covering only the questions that directly drive personalisation. Move everything else — detailed profile info, notification preferences, billing — to the user profile settings page where users can fill it in at their own pace.

How do I handle onboarding for users who signed up via social login?

Social login (Google, GitHub) does not affect onboarding — the flow triggers based on whether onboarding_responses.completed_at is null, regardless of how the user authenticated. On the first page load after any sign-up method, the completion gate checks the database and redirects new social login users to /onboarding exactly the same as email-signup users.

RapidDev

Need this feature production-ready?

RapidDev builds a custom onboarding flow into real apps — auth, database, payments — at $13K–$25K.

Book a free consultation

30-min call. No commitment.

Want this built for you?

We ship production apps at a fixed price — $13K–$25K, 6–10 weeks, source code yours. You've seen what it takes; we do it every week.

Get a fixed-price quote

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.