Browse docs
Funnels & glitch-finder
Find the steps where users drop off because something is broken — not just unpopular.
Most funnel tools tell you where people leave. GlitchReplay tells you why — because it already has the errors and the session replays. Send a handful of funnel events and the Funnels tab overlays, on each step, the share of sessions that hit a JavaScript error or a frustration signal (rage click, dead click, quick-back), then links straight to the recording. A step that both leaks and lights up red is broken, not just low-intent.
1. Drop in the tag (easiest)
Add one script and the funnel instruments itself — no per-event code. It autocaptures pageviews, clicks (with their text/href), and form submits, and links each to the active session replay.
<script src="https://glitchreplay.com/gr.js"
data-dsn="https://<key>@glitchreplay.com/prj_xxx"></script>The tag coexists with your existing SDK — if you already load the GlitchReplay (Sentry-compatible) SDK for errors and replay, the tag detects it (via window.__GLITCHREPLAY__ / window.Sentry) and only layers autocapture on top, so nothing double-captures. Config via data-*: data-autocapture="off" (disable click/pageview capture if you instrument by hand), data-capture-text="off" (omit element text), data-identify-email="on" (read an email field on submit). For the events a tag can't see from the DOM — server-confirmed signups, payments — use the escape hatch:
// Backend-confirmed conversions the tag can't see from the DOM:
window.glitch.track("signup_complete", { plan: "pro" });
window.glitch.identify({ id: "u_123", email: "a@b.com" });
// Turn any element into a named event without code:
// <button data-gr-track="upgrade_clicked">Upgrade</button>2. Or instrument manually
Prefer explicit events (and named conversions that match your backend)? Send them yourself. Same DSN and envelope endpoint as your errors — the only difference is the item type analytics, no extra SDK. Drop this helper in (replace the values with your DSN from Project → Setup):
import * as Sentry from "@sentry/browser"; // the SDK you already load for errors + replay
// Replace with the values from Project → Setup.
const PROJECT_ID = "prj_xxxxxxxxxxxx";
const PUBLIC_KEY = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
const ENDPOINT = `https://glitchreplay.com/api/${PROJECT_ID}/envelope/?sentry_key=${PUBLIC_KEY}`;
let userId = null;
let userEmail = null;
function id() {
return (crypto.randomUUID?.() || String(Math.random())).replace(/-/g, "");
}
function stored(key, store) {
let v = store.getItem(key);
if (!v) { v = id(); store.setItem(key, v); }
return v;
}
// Call once you know who the user is, so events stitch from anonymous -> known.
export function identify(user) {
userId = user?.id ?? null;
userEmail = user?.email ?? null;
}
// Fire-and-forget. Never throws. SSR-safe.
export function track(name, props = {}) {
if (typeof window === "undefined") return;
try {
const eventId = id();
let replayId;
try { replayId = Sentry.getReplay?.()?.getReplayId?.(); } catch {}
const body = [
JSON.stringify({ event_id: eventId, sent_at: new Date().toISOString() }),
JSON.stringify({ type: "analytics" }),
JSON.stringify({
event_id: eventId,
name,
source: "web",
ts: Math.floor(Date.now() / 1000),
anon_id: stored("gr_anon_id", localStorage),
session_id: stored("gr_session_id", sessionStorage),
user_id: userId,
user_email: userEmail,
path: location.pathname,
referrer: document.referrer || undefined,
// replay_id links a funnel drop-off to the exact session recording.
props: { replay_id: replayId, ...props },
}),
].join("\n");
const blob = new Blob([body], { type: "text/plain" });
if (!navigator.sendBeacon(ENDPOINT, blob)) {
fetch(ENDPOINT, { method: "POST", body, keepalive: true });
}
} catch {
// never throw from analytics
}
}Then call track() at the moments that matter:
track("page_view");
// On your signup form
track("signup_start");
// ...on success
track("signup_complete");
identify({ id: user.id, email: user.email });
// Your activation moment — the thing that means "they got value"
track("first_project_created");
// Revenue
track("checkout_start", { plan: "pro" });
track("checkout_complete", { plan: "pro" });Tag + manual together? Fine — turn off the tag's autocapture (data-autocapture="off") so it only handles errors/replay, or leave both on: the server soft-dedupes identical events (same session + name + path within ~2s), and a funnel step counts one event, so nothing double-counts.
The helper reuses the GlitchReplay (Sentry-compatible) browser SDK you already load for errors and replay — that's where replay_id comes from, and it's what makes the one-click “Watch” links work. If a session has no active replay the event still counts toward the funnel; it just can't be correlated to errors or frustration (it shows as untracked).
Event payload
Each event is one envelope item with header { "type": "analytics" } and a JSON body. Fields:
name— required. The event name, e.g.signup_complete. Keep names stable; they are the funnel steps.anon_id— stable per-browser id (survives sessions). Anchors a visitor before they sign in.session_id— per-tab id. Used to group a session and to deep-link replays.user_id/user_email— set viaidentify()once known, so a journey stitches anonymous → known.ts— unix seconds.path,referrer,source— optional context.props— any extra fields. Includeprops.replay_id(fromSentry.getReplay().getReplayId()) to link the session replay.utm_*here become acquisition attribution.
3. Define your funnel
Out of the box your funnel is auto-derived from your most common events, so it works the moment data arrives. To shape it, go to Project → Settings → Funnel and define ordered steps. Each step matches an event — one you send (signup_complete) or an autocaptured one ($pageview, $autocapture, $form_submit) — with an optional contains filter that narrows it to events whose URL path or clicked text contains a string. That means you can build a funnel like “$pageview contains /signup” → “$autocapture contains Start free” entirely from autocaptured data, no code.
4. Read the funnel
Open Project → Funnels. Each step shows:
- Reach & drop-off — distinct users who fired the event, the percentage of the first step, and the drop from the previous step.
- Error / frustration badges — of the replay-tracked sessions at that step, the share that hit a JS error or a frustration signal. This is the glitch overlay: a leaky step with a red badge is your highest-leverage fix.
Below the funnel, Top glitches hurting the funnel ranks the issues (JS errors, rage/dead clicks, accessibility violations) affecting the most sessions, each linking to the issue and its replays. The Recent sessions table tags every session error / friction / clean / untracked with a one-click jump to the recording.
5. From a leak to a fix
The workflow this is built for:
- Scan the funnel for a step that both leaks (high drop-off) and shows a high error or frustration badge.
- Open the issue from Top glitches, or click a flagged session's replay, to see what actually broke.
- Confirm cause in the recording — the badge is correlation; the replay is proof.
How the overlay works
Funnel sessions are joined to errors and frustration by replay_id — the same Sentry replay id GlitchReplay stores on every replay and every linked error. So the overlay requires session replay to be enabled on the project (it is by default). Two honest limits, surfaced in the UI rather than hidden:
- Only replay-tracked sessions are measurable. A session with no
replay_id(replay sampled off, or an event fired before the recorder started) can't be tied to errors — it readsuntracked, never silently “clean”. - Reach-based, not strictly ordered. Each step counts distinct users who fired that event in the range; it does not enforce that step N happened after step N−1 for the same user.
Notes
- Funnel events are ingested through the envelope endpoint but stored separately from errors — they do not count toward your monthly error-event total.
- Bot traffic is filtered server-side, the same as errors.
- Date range (24h / 7d / 30d) applies to the whole page, including the glitch overlay and Top glitches.