# Funnels

A funnel is the list of steps in a journey, such as sign-in or checkout. It shows how many sessions reached each step, where the rest dropped out, and how long each step took.

## Build a funnel

A step is a screen or a named action, in order. In Studio, open the Product screen’s **Funnels** tab and add steps; it suggests the screens and actions your sessions already have. From the terminal:

```shell
squiggle product funnel save "Sign in" \
  --step screen:index \
  --step screen:stress \
  --step action:sign_in \
  --step screen:checklist
```

A funnel needs at least two steps. A step can accept more than one name, for a journey with two routes through it: `--step "screen:login|sso"`. Action names are the ones you gave `defineAction`.

Funnels are saved in `.squiggle/perf/config.json`, so Studio, the terminal and your agent all use the same ones.

## Read it

```shell
squiggle product funnel run "Sign in" --last 50
```

Output:

```text
Sign in 2/50 sessions converted
  index      ████████████████████████████  20 (100%)  med —
  stress     ████░░░░░░░░░░░░░░░░░░░░░░░░    3 (15%)  med 3.5s
  sign_in    ███░░░░░░░░░░░░░░░░░░░░░░░░░    2 (10%)  med 1.4s
  checklist  ███░░░░░░░░░░░░░░░░░░░░░░░░░    2 (10%)  med 51ms
  share of the first step · median time to reach
```

Each row is how many sessions reached that step, as a share of the first step, and the median time it took to get there from the step before. Add `--json` to also get the 90th percentile: a step most people finish in four seconds while some take four minutes is a different problem from one everyone finishes in four seconds.

In Studio you can also limit how long a conversion may take. Leave it empty to count a conversion anywhere in the session.

A session reaches a step when it happens after the step before it. Whatever happens in between doesn’t matter, and if a session starts the funnel more than once, its furthest attempt counts. Build a funnel over ten sessions to see which events each one matched:

## Compare groups

Split a funnel by any trait you set with `identify()`, such as plan, experiment variant or app version, to get one funnel per value:

```shell
squiggle product funnel list
squiggle product funnel run "Sign in" --segment-by plan
squiggle product funnel rm "Sign in"
```

Or split it by something your app was holding, if it sends [state samples](https://squiggle.sh/docs/reference/sdk). You get two funnels: sessions that reached that state, and sessions that sent samples but never did.

```shell
squiggle product funnel run "Checkout" --where "zustand:useCart.items#len >= 3"
```

A state is a feature path, a comparison (`>=`, `<=`, `>`, `<`, `=`, `!=`) and a value. A text value, such as an allowed theme, compares only with `=` or `!=`. `squiggle product state` lists the paths your sessions have; see [State paths](https://squiggle.sh/docs/product#state-paths). Sessions that sent no state samples are counted apart and are in neither funnel, because nothing is known about what they held.

Pass either `--segment-by` or `--where`, not both. `--where` works the same way on `screens`, `friction`, `retention`, `usage` and the other `squiggle product` reports. Your agent passes it as `whereState` to `product_funnel_eval`, `product_screens`, `product_friction` and `product_retention`.

> **Funnels survive a redesign**
>
> Steps name screens and actions, not positions on the screen, so a funnel keeps working when the layout changes. Rename a screen or an action, though, and update the funnel to match.
