Self-hosted customer journey analytics.

Lyraflow records what people do in your product, stitches anonymous visits to known accounts, and lets you ask who did what.

It runs on your own machine under Docker, and nothing leaves it.

fair-code · Docker and Docker Compose, nothing else · two databases, one container each

About five minutes

Four steps to a running install, most of it waiting for Docker.

Two containers

Events go to ClickHouse and identity to Postgres. Old events expire on a schedule you set.

Every limit has a number

It is written down, in the documentation, and the ones with known slack say so.

Knowing who someone is

A visitor browses anonymously, signs up two weeks later, then uses your product from a phone. Lyraflow ties all of that to one person, and reads their history back stitched.

If two people share a device, each event is attributed to whoever was signed in at that moment — not to whoever used it last.

The Lyraflow event feed. One row is expanded into two columns: the attributes the event arrived with, including its anonymous device id and its user id, and the event's own properties.
One event, opened: the anonymous device it arrived from and the person it belongs to, on the same row.

Asking who did what

Build a filter tree in the browser — traits, context, lifecycle bounds, and behaviours with predicates of their own — and read it back in plain English before you trust it. Clicking a person shows what is known about them: where they are, what they arrived through, and the traits identify() has set.

A saved segment's count is the server's cache from its last evaluation, shown with the instant it was taken and never passed off as current. Each run is a real scan, so nothing re-evaluates itself just because you opened a list.

A saved Lyraflow segment, its filter tree rendered underneath the name as an English sentence, above the person count and a list of members with one of them expanded into attributes and traits.
A three-part filter, read back as a sentence, with one of the people it matched opened.

Where people stop

A funnel is a list of events in order, over a window you choose: how many people reached each step, and how many did not go on. It is drawn as a flow rather than a bar chart, so the people who leave are a band that peels away and stays visible instead of a gap you have to infer.

A step can be marked optional, and the people who skipped it still count at every step after it — without that, one detour through a help page reads as a drop-off. The window is how long someone has to finish, and it is part of the funnel rather than part of the question you ask this time.

The Lyraflow funnels screen: four steps drawn as a flow diagram, with one optional step branching off the main path and rejoining it, and the people who stopped at each step peeling away as separate bands.
One step is marked optional, so the people who skipped it still count at the steps after it, and every band is as wide as the number of people who took that path.

Whether they came back

Of the people who did one thing in a period, how many came back and did another in the periods after it. Both events are yours to pick, and each takes a condition — so on a site where every navigation is one event name, “viewed the home page, then came back and registered” is a question you can actually ask.

A period that had not finished when the grid ran shows a dash, never a zero, and the screen says how many cells are waiting. Reporting an unfinished week as nought draws a collapse in the newest cohorts, in exactly the corner a reader scans for a trend.

The Lyraflow retention screen: the two events that define the grid above it, then one row per weekly cohort, its cells shaded by how many of that week's people came back.
The empty corner is periods that have not finished yet, not people who left. Shading is relative to the strongest cell in the grid, which the screen says underneath it.

How a number splits

An event over time, broken down by a property it carries or by a column of the event itself. Events with no value there are kept as their own series rather than dropped, so the parts still add up to the whole and a number here can be checked against the feed.

Each value gets its own panel, and every panel is drawn on one shared scale — so a series ten times another’s size looks it. Ten at most; anything smaller is summed into one labelled series and the screen says how many values that was.

The event can be narrowed before it is split, with the same conditions a funnel step and a retention grid take. Without that, a site whose every navigation is one event name can only chart “a page was viewed”; with it, the registration page, by referrer is one chart. A half-written condition blocks the run rather than being quietly dropped, because a filter that silently disappears answers a wider question than the one on screen.

The Lyraflow trends screen: one event split by one of its properties, drawn as a grid of small line charts with one panel per value.
One panel per value rather than one chart with a colour key, and a single scale across all of them, so their heights are comparable.

A report you can keep

A retention grid or a trend can be named and saved, the same way a funnel is, and reopened from a list that shows what each one asks rather than only what it is called.

What is saved is the question, never the window. “Signups by day, last week” and “signups by day, this month” are one report asked twice. The consequence is deliberate and visible: reopen a saved report into a range it cannot answer — a minute-by-minute chart across a year — and the screen says so and refuses, rather than quietly widening the definition you saved to fit the window you are in.

curl https://analytics.example.com/v1/trends \
  -H "x-lyraflow-server-key: $LYRAFLOW_SERVER_KEY"

One person, end to end

Every id ever bound to someone — the ids you sent and the devices they were seen on — with when they were first and last seen, what they have done, and the traits they carry. Reachable from a feed row, from a segment’s member list, and by searching when you have no id in hand at all.

It reads the same stitched identity every other screen reads, so a person who looks like two people here looks like two people in a funnel as well. That is the point of showing it: identity problems are much easier to see on one person than in an aggregate.

The Lyraflow person screen: one person's canonical id, the user ids and device ids bound to it, their traits, and their events in order.
The ids on the left are the whole of what Lyraflow knows this person by. Two rows where you expected one is an identity problem you can see.

Deleting someone completely

Deleting a person erases the underlying rows, and every read path — segment counts, member lists, person reads, exports — is filtered from the instant the request is accepted, not when the purge finishes. A person deleted a second ago is already invisible.

curl -X DELETE https://analytics.example.com/v1/persons/user-42 \
  -H "x-lyraflow-server-key: $LYRAFLOW_SERVER_KEY"

Try it

Four steps. About five minutes, most of it waiting for Docker. You need Docker and Docker Compose v2.21.0 or newer. Nothing else.

git clone https://github.com/lyraflow/lyraflow.git
cd lyraflow
./install.sh

That generates passwords into .env, starts three containers, and waits until the app answers on port 3000. Pass a domain name instead and it serves HTTPS itself.

What is not there yet

v0.12 has no dashboards: every report is opened and read on its own screen, and there is no page that puts several of them side by side. Journeys and path analysis are still ahead.

There is no breakdown on a funnel, and no way to split a retention grid by anything — the split that trends have does not exist on either of the other two reports. Alerting, scheduled exports and any form of email digest are not there at all.

Read the source

Every claim on this page is in the README, with the numbers attached.