> ## Documentation Index
> Fetch the complete documentation index at: https://docs.wendung.app/llms.txt
> Use this file to discover all available pages before exploring further.

# Funnel tracking

> Model a user journey as named step events, attach traits with identify(), and read conversion and drop-off per stage in the dashboard.

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.

<Note>
  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.
</Note>

## 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()`.

```ts theme={null}
// Set once; attached to every event that follows
Wendung.identify('user_0042', {
  email: 'alice@example.com',
  plan: 'trial',
  source: 'product_hunt',
})

// Per-event properties only; plan and source come from traits
Wendung.step('signup_completed')
Wendung.step('plan_selected', { selected_plan: 'pro' })
```

## 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.

```ts theme={null}
// After signup form submits successfully, in your signup component
Wendung.identify(user.id, {
  email: user.email,
  plan: user.plan,
  source: user.source,
})
Wendung.step('signup_completed')
```

```ts theme={null}
// After the user saves their profile, in your profile form handler
Wendung.step('profile_filled')
```

```ts theme={null}
// After the first project is created, in your project creation flow
Wendung.step('first_project_created', { template: 'blank' })
```

```ts theme={null}
// After a teammate invite is sent, in your team invite component
Wendung.step('invited_teammate', { invite_method: 'email' })
```

```ts theme={null}
// Once the user has met your activation criteria, usually triggered server-side
// from your own logic and signaled to the client
Wendung.step('activated')
```

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

<AccordionGroup>
  <Accordion title="Do I have to create the funnel in the dashboard before I start tracking?">
    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.
  </Accordion>

  <Accordion title="What if a user skips a step?">
    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.
  </Accordion>

  <Accordion title="Can I track the same step multiple times?">
    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.
  </Accordion>

  <Accordion title="How do I track anonymous users?">
    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.
  </Accordion>

  <Accordion title="Can I use the same step name in multiple funnels?">
    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.
  </Accordion>
</AccordionGroup>

## What's next

<CardGroup cols={2}>
  <Card title="Identity and sessions" icon="user" href="/concepts/identity">
    How `identify()`, `reset()`, and sessions tie events to users.
  </Card>

  <Card title="API reference: step()" icon="code" href="/api-reference/step">
    Full reference for the `step()` method, including property constraints.
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.