Track product events without identifying people
Declare each product event and its props, send events with consent, and query active users, funnels and retention from the built-in store.
Status Status is where the work is: Planned (in a milestone, not started), Building (tickets in progress), Preview (usable, may change), then the stability it ships at.
Stability The promise an interface ships with.
- Stable Follows semantic versioning; breaking changes need a new major, 12 months of overlap and a codemod.
- Beta Usable in production; may change before it becomes stable.
- Experimental May change or go away; needs an explicit opt-in in main.yaml.
You want to know which features people use and where they drop off, without learning who anyone is. This guide declares two events, tracks them, and reads the numbers back. It takes about 20 minutes.
Built for 0.3 (spec analytics). The built-in store keeps events in the tenant's region; PostHog and an OpenTelemetry collector are optional drivers.
You need
- A MainSDK app with privacy consent set up for the
analyticspurpose.
1. Declare the events
analytics:
to: [builtin]
without-consent: drop
min-group: 10
events:
checkout.started:
purpose: analytics
props: { plan: "enum(free, pro)", items: int, coupon: "string?" }
lesson.finished:
purpose: analytics
props: { subject: "enum(math, science, reading)", minutes: int }Only declared events are accepted. A prop that would identify a person, such as email or ip, fails the check with MAIN-ANALYTICS-0011.
2. Track the events
"use server";
import { analytics, auth } from "mainsdk";
export async function startCheckout(req: Request, plan: string) {
await auth.require(req);
analytics.track("checkout.started", { plan, items: 3 });
}The user and tenant come from the request. You never pass them in.
3. Read the numbers
import { analytics } from "mainsdk";
const funnel = await analytics.query({
kind: "funnel", steps: ["checkout.started", "lesson.finished"], within: "7d",
from: "2026-10-01", to: "2026-10-31",
});Rows for fewer than min-group people are hidden, and the result says how many were hidden.
From the terminal, mainsdk analytics asks the same questions across every tenant:
npx mainsdk analytics --query funnel --steps checkout.started,lesson.finished --within 7dCheck it worked
mainsdk checkpasses with noMAIN-ANALYTICS-0010or0011findings.- Tracking an event you did not declare throws
MAIN-ANALYTICS-0001in development. - Without consent for the purpose, the event is dropped and no row is stored.
If it fails
MAIN-ANALYTICS-0002: props do not match the declared types. Send values that match.MAIN-ANALYTICS-0004: a query names an undeclared event or uses abyits kind does not take.MAIN-ANALYTICS-0007:analytics.queryneedsbuiltininanalytics.to.
Prompt for your coding agent
Track
checkout.startedandlesson.finishedwith MainSDK'sanalyticsinterface. Declare their props with types, keep identifying fields out, and add a funnel query that a non-technical admin can read in the app.
Was this page helpful?