Features
Reserved events
The event names Flowgrid understands out of the box. Most arrive on their own; the rest are one call each, and every one of them lights up a part of your dashboard without any setup there.
Five events that matter
Clicks, page speed, forms, searches and errors are collected for you. What Flowgrid can't see is what happens inside your product — so these few calls are where your winning metrics come from. Add them first.
| Question | Event | Call |
|---|---|---|
| How many people sign up? | signup_completed | fg.signup(userId, "google") |
| How many come back? | login_completed, active_user | fg.login(userId) · fg.setUser(userId) |
| Do new users reach the “aha” moment? | first_action | fg.track("first_action", { … }) |
| Do they finish onboarding? | onboarding_completed | fg.track("onboarding_completed", { … }) |
| Are we growing MRR? | subscription_start / _cancel | fg.trackSubscription({ event: "start", … }) |
1import { fg } from "./flowgrid"; // your FlowGrid.init() instance
2
3// 1. Signups and logins
4fg.signup(user.id, "google");
5fg.login(user.id, "google");
6
7// Every page load once the user is known: keeps DAU / WAU / MAU right
8fg.setUser(user.id, { email: user.email, name: user.name });
9
10// 2. Activation — the first thing that proves your product's value
11fg.track("first_action", {
12 userId: user.id,
13 actionName: "first_report_created",
14 timeSinceSignup: Date.now() - signedUpAt, // ms
15});
16
17// 3. Onboarding — one call per step, one when it's done
18fg.track("onboarding_step", {
19 userId: user.id, stepId: "connect", stepName: "Connect data", stepOrder: 2, totalSteps: 4,
20});
21fg.track("onboarding_completed", {
22 userId: user.id, totalTimeSpent: Date.now() - onboardingStartedAt, stepsCompleted: 4,
23});Tip
Using a hosted sign-in (Google, SSO, magic links)? Call fg.markSignup() before the redirect and fg.setUser() after it — the signup fires exactly once. See Signups & Logins.
Reserved vs your own
Your own events go through fg.capture(name, properties) and keep their exact name. The names on this page are taken: capture()refuses them (and the dashboard won't let you define one), so a click you name click_eventcan never be counted as Flowgrid's click stream and inflate it. Send a reserved event through its helper or fg.track() instead.
1// ✅ Your own event — any name that isn't reserved
2fg.capture("report_exported", { format: "csv" });
3
4// ✅ A reserved event — through its helper or track()
5fg.signup(user.id, "email");
6
7// ❌ Refused: "signup_completed" is a name Flowgrid sends itself
8fg.capture("signup_completed", { plan: "pro" });Collected automatically
The flowgrid.js script sends these on its own. There is nothing to call, and they arrive within minutes of install.
| Event | How it's sent | Shows up in |
|---|---|---|
| click_event | Every link and button click | Clicks, Heatmaps |
| heatmap_click | Every click, with its position | Heatmaps |
| heatmap_rage_click | Repeated clicks on one spot | Heatmaps, Session Replays (rage clicks) |
| heatmap_scroll | How far each page is scrolled | Heatmaps (scroll map) |
| heatmap_movement | Mouse movement, sampled | Heatmaps (move map) |
| web_vitals | LCP, INP, CLS, FCP and TTFB per page | Performance (page speed) |
| form_event | Every form submit, with its field names and completion time | Form Events, funnel steps |
| search_event | Searches in a search form or a search input | Searches, funnel steps |
| error_event | Uncaught errors and unhandled promise rejections | Performance, Session Replays (errors), flag guardrails |
| attribution_touchpoint | Arrivals from a campaign (UTM) or a referrer | Campaign touches, attribution |
| attribution_conversion | Return from a Stripe, Lemon Squeezy or Polar checkout | Revenue attribution |
Sent by an SDK helper
One call each. The helper fills in the visitor, session and timing; you pass who the user is.
| Event | How it's sent | Required | Shows up in |
|---|---|---|---|
| signup_completed | fg.signup(userId, method, source?) | userId, method | Signups, Acquisition Funnel “Signed up” |
| login_completed | fg.login(userId, method?) | userId | Logins |
| active_user | fg.setUser(userId, traits) — once a day per user | userId | Active Users (DAU / WAU / MAU) |
| identify_user | fg.identifyUser(userId, traits) | userId | Identified visitors, Users |
| update_traits | fg.updateTraits(userId, traits) (server) | userId, traits | Users (profile) |
| alias_user | fg.aliasUser(previousId, userId) (server) | previousId, userId | Users (merged history) |
| reset_identity | fg.logout() / fg.reset() | — | Ends the identified session |
| subscription_start | fg.trackSubscription({ event: "start", … }) (server) | subscriptionId, userId, plan, mrr, billingCycle | MRR and churn |
| subscription_change | fg.trackSubscription({ event: "change", … }) (server) | subscriptionId, userId, previousPlan, newPlan, previousMrr, newMrr, changeType | MRR and churn |
| subscription_cancel | fg.trackSubscription({ event: "cancel", … }) (server) | subscriptionId, userId, plan, lostMrr, immediate | MRR and churn |
| subscription_renew | fg.trackSubscription({ event: "renew", … }) (server) | subscriptionId, userId, mrr | MRR and churn |
| trial_converted | fg.trackSubscription({ event: "trial_converted", … }) (server) | subscriptionId, userId, plan, mrr | MRR and churn |
| trial_expired | fg.trackSubscription({ event: "trial_expired", … }) (server) | subscriptionId, userId | MRR and churn |
Sent with track()
No dedicated helper: call fg.track() with the reserved name. TypeScript checks the required properties for you.
| Event | How it's sent | Required | Shows up in |
|---|---|---|---|
| onboarding_step | fg.track("onboarding_step", …) | userId, stepId, stepName, stepOrder, totalSteps | Onboarding steps, funnel steps |
| onboarding_completed | fg.track("onboarding_completed", …) | userId, totalTimeSpent (ms), stepsCompleted | Onboardings, funnel steps |
| first_action | fg.track("first_action", …) | userId, actionName, timeSinceSignup (ms) | First actions, funnel steps |
| funnel_step | fg.track("funnel_step", …) | funnelId, step, stepNumber | Event counts |
| funnel_complete | fg.track("funnel_complete", …) | funnelId | Funnel completions, funnel steps |
| funnel_abandon | fg.track("funnel_abandon", …) | funnelId, lastStep | Event counts |
| prompt_submitted | fg.track("prompt_submitted", …) | promptId, promptType | AI prompts sent, funnel steps |
| prompt_response | fg.track("prompt_response", …) | promptId, responseTime, completed | Event counts |
| prompt_feedback | fg.track("prompt_feedback", …) | promptId, feedbackType | Event counts |
| prompt_conversation | fg.track("prompt_conversation", …) | conversationId, userId, turnCount, totalDuration | Event counts |
| user_activity | fg.track("user_activity", …) | userId | Event counts |
Internal names
Used by the SDK and the tracker to talk to Flowgrid. You never send these — they are reserved so your own events can't be mistaken for them.
| Event | How it's sent |
|---|---|
| page_load | Page views |
| page_view | Page views from the npm SDK |
| page_exit | Leaving a page |
| time_on_page | Time spent on a page |
| scroll_depth | Scroll milestones |
| session_start | A new session |
| session_update | Session progress |
| session_heartbeat | Session keep-alive |
| session_end | End of a session |
| resource_timing | Slow assets |
| api_timing | Slow API calls |
| lifecycle_change | A user moving lifecycle stage |
| group_user | Linking a user to an account |
| custom_event | The envelope for legacy track() calls with your own name |
| batch_events | Several events in one request |
| create_tracking | Registering a feature |
| define_funnel | Funnel definition calls |
| analyze_funnel | Funnel definition calls |
| compare_funnel | Funnel definition calls |
| list_funnels | Funnel definition calls |