Back to BlogArchitecture & Engineering

Building a SaaS Onboarding Flow That Activates Users

Rupak Amin

Founder & Lead Engineer, RAITHub

9 min read

RAITHub ships and tests production software. See QA as a Service or talk to us.

A SaaS onboarding flow is the guided path from sign-up to the first time a user gets real value from your product. Build it around one measurable activation event, store each step's state on the server so a refresh never loses progress, make every step resumable and idempotent, seed useful sample data, and measure drop-off per step so you fix the real wall, not a guessed one.

If you would rather have onboarding built into your product for you, see how RAITHub would build this below.

What is a SaaS onboarding flow, and what makes it "good"?

Onboarding is everything between "account created" and "this person now uses the product". A good flow is judged by one number: activation, the share of new accounts that reach a defined moment of value within a set window. Everything else, including the checklist UI, the tooltips and the welcome email, exists only to move that number.

The common mistake is to build onboarding as a sequence of screens with no server-side truth behind it. The user refreshes, loses their place, and leaves. The fix is to treat onboarding as data: a record of which steps each user has completed, read on every page load, that the UI renders from.

How do you define the activation event?

Pick the first action that reliably predicts retention, not the first action that is easy to track. For a project tool it might be "invited one teammate and created one project"; for an analytics product, "connected a data source and saw a chart with their own data". Write it down as a precise, queryable condition before you build anything.

Product typeWeak signal (avoid)Activation event (use)
Team collaborationSigned upCreated a workspace and invited one member
AnalyticsViewed the demo dashboardConnected a source and saw own data
Billing or invoicingAdded company detailsSent the first real invoice
Developer APIRead the docsMade a successful authenticated API call

Team setup is part of activation for most B2B products, so onboarding and invitations overlap. The invitation mechanics are in team workspaces and invitations for a B2B SaaS.

What should the onboarding data model look like?

Store the state of each step per user or per account, not a single "onboarded" boolean. A boolean cannot tell you where people stop, cannot be resumed, and cannot be changed when you add a step later.

-- One row per user per onboarding step. The UI renders from this.
CREATE TABLE onboarding_steps (
  user_id      uuid NOT NULL,
  step_key     text NOT NULL,          -- 'create_project', 'invite_member'
  status       text NOT NULL DEFAULT 'pending', -- 'pending' | 'done' | 'skipped'
  completed_at timestamptz,
  PRIMARY KEY (user_id, step_key)
);

-- The activation fact itself, written once, used for the activation metric.
CREATE TABLE user_activation (
  user_id      uuid PRIMARY KEY,
  activated_at timestamptz NOT NULL,
  signed_up_at timestamptz NOT NULL
);

Keep the list of steps in code, not in the database, so a deploy, reviewed in git, is the only way to change the flow. The database holds progress; the code holds the definition. If your product is multi-tenant, scope these rows to the tenant like any other table, using the pattern in the Postgres row-level security guide.

How do you make a step resumable and safe to repeat?

Every step must be idempotent: clicking "create my first project" twice, or reloading mid-step, must not create two projects. Mark the step done inside the same transaction that performs the action, keyed so a repeat is a no-op.

// Complete a step and perform its action atomically. Safe to call twice.
export async function completeStep(db: Tx, userId: string, step: string, action: () => Promise<void>) {
  // INSERT ... ON CONFLICT DO NOTHING is the idempotency guard.
  const first = await db.query(
    'INSERT INTO onboarding_steps (user_id, step_key, status, completed_at) ' +
    "VALUES ($1, $2, 'done', now()) " +
    'ON CONFLICT (user_id, step_key) DO NOTHING RETURNING user_id',
    [userId, step],
  )
  if (first.rowCount === 0) return // already done; do not run the action again
  await action()
}

Resumability then comes for free: on every load, read the rows, and render the first step whose status is not done. The user can close the tab at any point and return to exactly where they were.

Should onboarding use a product tour, a checklist, or empty states?

Use empty states first, a checklist second, and a tour rarely. An empty state that shows what to do where the work happens beats a modal tour that interrupts, because people skip tours and then face a blank screen anyway.

  • Empty states. Every list and dashboard gets a designed empty state with one clear action and, where it helps, pre-seeded sample data the user can edit or delete.
  • A persistent checklist. A small "getting started" list, driven by the onboarding_steps rows, that the user can dismiss. It shows progress and survives reloads because it reads from the server.
  • Sample data. Seed one example project, report or record so the product is not empty on day one. Flag it as sample so it never pollutes real analytics.

Email is part of onboarding too: a welcome message, then nudges for the steps a user has not finished. Trigger those from the same step data, through the notification layer described in building a SaaS notification system.

Buy, build or hire?

OptionExamplesChoose this whenWatch out for
Onboarding SaaS widgetHosted product-tour and checklist toolsYou want tours and tooltips live this week without engineering timeState lives in their system, not yours; activation data is harder to join to your own
No-code flow builderDrag-and-drop in-app guidesNon-engineers will edit the copy and order oftenLogic that depends on real account state is awkward; another script on every page
Build it inThe data model and checks aboveSteps depend on real backend state and you want activation in your own databaseYou own the measurement and the resume logic; budget for both
Hire a team to build itRAITHub or another studioOnboarding, invitations and notifications must work together from the first releaseInsist the handover includes the activation metric and the drop-off dashboard

Do-it-yourself estimate: 3–6 days for the step model, idempotent actions, a checklist UI, sample-data seeding and the activation metric, if you already have auth and a tenant model. The main risk is building screens with no server-side state, so progress is lost on refresh and you cannot see where users drop off.

How do you measure and improve onboarding?

Measure activation rate and per-step drop-off, then fix the earliest step with the steepest fall. A funnel that reports "40% activate" without per-step numbers tells you nothing actionable.

  • Activation rate: activated accounts divided by sign-ups, in a fixed window such as 7 days.
  • Per-step completion: for each step, the share who reach it and the share who finish it. The gap is where to focus.
  • Time to activate: the median hours from sign-up to the activation event.

These queries run straight off the onboarding_steps and user_activation tables, so you need no extra analytics vendor to get started. Reducing early drop-off is one of the engineering levers in churn you can fix with engineering.

How RAITHub would build this

As part of a new SaaS build, or added to an existing product, scoped in writing after the free audit.

  • Activation definition: one measurable event agreed with you, written as a queryable condition.
  • Step model and UI: server-side step state, resumable and idempotent, driving empty states and a dismissible checklist.
  • Sample data and emails: seeded example records flagged as sample, plus welcome and nudge emails fired from step data.
  • Measurement: activation rate, per-step drop-off and time-to-activate, queryable in your own database.

Timeline: inside a new product, onboarding is part of the 4–6 week fixed-scope SaaS build; added to an existing app, it is a short scoped piece within the 6–12 week backend range, with the exact scope in the quote.

You receive: automated tests and CI for the idempotent steps, handover docs, and full IP assigned to you under NDA.

Next step: a free 15-minute technical audit, then a written fixed quote. See the SaaS development service, or book the audit.

Frequently asked questions

What is user activation in SaaS onboarding?

Activation is the point where a new account first gets real value, defined as a specific, measurable event such as "invited a teammate and created a project". It predicts retention better than sign-up, so onboarding is built and measured against it.

Where should onboarding progress be stored?

On the server, as one row per user per step, read on every load. A boolean or browser-only state cannot be resumed after a refresh and cannot tell you where users drop off, which is the whole point of measuring onboarding.

How do I stop an onboarding step from running twice?

Mark the step done inside the same transaction as its action, guarded by an INSERT ... ON CONFLICT DO NOTHING so a repeat is a no-op. Then the action and the "done" flag always agree, even if the user double-clicks or reloads.

Is a product tour the right way to onboard users?

Usually not on its own. People skip tours and then face a blank screen. Designed empty states and seeded sample data, backed by a persistent checklist that reads from the server, tend to move activation more than a one-time tour.

How long does it take to build a SaaS onboarding flow?

Around 3 to 6 days to build the step model, idempotent actions, a checklist UI, sample-data seeding and the activation metric, assuming auth and a tenant model already exist. Inside a new build it is part of the 4 to 6 week fixed-scope SaaS project.

SaaS onboardingUser activationOnboarding flowChecklist UIPostgreSQLTypeScript

Ready to discuss your project?

Book a free 15-minute technical audit with our engineering team.