Skip to main content
A funnel is an ordered list of named steps, for example signup_completed → profile_filled → first_project_created → invited_teammate → activated. You emit those steps from your code with step() and define the same list in the dashboard. Events are matched to the funnel by name, and each stage shows its conversion rate, drop-off, and time to complete.

How it works

A funnel has two sides, joined by name:
  1. In your code: you call step(name) at each point where a user reaches a milestone. Names are plain strings of 1 to 100 characters, with no schema and no predefined vocabulary.
  2. In the dashboard: you create the funnel and put those step names in order.
Start with the code. Step names are collected as events arrive, so by the time you create the funnel they are waiting in a dropdown. The dropdown lists names in the order users typically perform them, so picking top to bottom reproduces the real journey, and each name shows its usage count, which makes a typo with three events easy to spot. If you build the funnel in the dashboard first, the names you type there become the contract. Every step() call has to match them character for character, or the step stays empty. Each step() call records one event with a timestamp, the identified user if there is one, and any properties you attach. The server orders those events per user and builds the funnel from them.
You don’t need to call steps in order, and you don’t need to call every step. Users who skip steps show up as drop-offs in the funnel between the last step they hit and the next one they didn’t.

Traits vs properties

You attach metadata to events in two places, and the distinction matters for how you analyze your funnel later.
  • Traits go on the user via identify(). Use them for things that are true about the user across all events: plan, acquisition source, email, name. Traits ride on every event that follows until you call identify() again or reset(). Each identify() call replaces the identity and traits wholesale; nothing is merged.
  • Properties go on a single event via the second argument to step(). Use them for things specific to that one moment: which plan the user selected at this step, the button variant they clicked, a referrer from the current page.
As a rule of thumb: if you’d have to pass the same value to every step() call, it belongs in identify().

A typical onboarding funnel

Here’s what a five-step onboarding funnel looks like when instrumented across a real application. Each call happens in the context where the milestone actually occurs, not all in one block.
Once these events are flowing, create a funnel in the dashboard with these five step names in order. Conversion, drop-off, and timing show up for every user who enters, including the ones already tracked.

Naming best practices

Consistent names make funnels easier to read and compare over time.
  • Use snake_case. signup_completed, not SignupCompleted or signup-completed.
  • Use past-tense verbs. Events describe something that already happened. profile_filled, not fill_profile.
  • Be specific. first_project_created is more useful than project_event when you want to segment later.
  • Keep names stable. Renaming a step in your code without updating the funnel in your dashboard will break the match. Treat step names like API contracts.

FAQ

No. You can send step() events first and create the funnel afterward; every event is stored regardless. You just won’t see conversion numbers until the funnel and its step order exist in the dashboard.
Steps are independent events. Order is not enforced and no step is required. If a user never reaches invited_teammate, no event is recorded for it, and the funnel shows the drop-off between the last step they hit and this one.
Yes. Each step() call creates a new event with its own timestamp. If a user completes first_project_created twice, two events are recorded. The funnel counts each user once per step, using their first occurrence by default.
You can call step() before identify(). Events are recorded with userId: null and the current session ID. Once you call identify(), later events belong to that user, and the session’s earlier anonymous events are linked to them at query time, so the funnel counts the whole journey as one person. Anonymous events stay anonymous only when nobody identifies in that session.
Yes. Step names are strings; they have no owning funnel. If signup_completed is the first step in your onboarding funnel and also appears in a separate “trial-to-paid” funnel, a single step('signup_completed') call feeds both.

What’s next

Identity and sessions

How identify(), reset(), and sessions tie events to users.

API reference: step()

Full reference for the step() method, including property constraints.