Quickstart
Create your first metric, log the events it depends on, and put an alert on it — five minutes, end to end.
This walks you through the metric pipeline end to end: pick what to measure, log the underlying events, create the metric definition, and put a threshold alert on it. By the end you'll have a number you can watch while you ramp, and a rule that pages when it moves the wrong way.
The 5-minute path
define · log · attach · readCreate a conversion metric
Log the underlying event from your code
Raise a ticket when it drops
Watch the series on the dashboard
1. Pick what to measure
Before you create anything, answer one question: what number tells you the change worked?
For a checkout-flow rewrite: probably purchase_conversion — did exposed users buy? For a new
paywall: probably subscription_conversion — did they sign up? For a homepage redesign:
typically session_engagement — did they click past the fold?
Pick one. Two is fine if they're closely related. Five means you haven't decided yet — go back and decide.
The metric needs to be:
- Computable from events you already log (or are willing to start logging).
- Specific to the change — not "DAU," which moves for a hundred reasons.
- Reasonable to see — on 1,000 users a day, a 1% move is inside the daily noise. Run
shipeasy metrics series <name>first and look at how much the number already wanders before you decide what counts as a real move.
2. Define the metric
The simplest case — did event X happen for this user at least once?
shipeasy metrics create purchase_conversion \
--event-name purchase \
--query 'count_users(purchase)'You now have a metric definition. It does nothing on its own; it tells the analysis pipeline how to aggregate the underlying events per user.
For a revenue metric (sum the revenueCents property across purchase events per user):
shipeasy metrics create revenue_per_user \
--event-name purchase \
--query 'sum(purchase, revenueCents)'For more aggregation types, see Aggregations.
3. Log the underlying events
The metric is a rule for aggregating events. The events themselves come from your code:
import { flags } from "@shipeasy/sdk/server";
export default async function CheckoutSuccess({ order }: { order: Order }) {
flags.track(order.userId, "purchase", {
revenueCents: order.totalCents,
currency: order.currency,
channel: order.acquisitionChannel,
});
return <ThankYou />;
}A few rules that matter:
- The first argument is the
userId.flags.trackrequires it as a positional arg — it is what makescount_userscountable. - Properties become filterable. You can later add a metric like
"organic-channel purchases" by filtering on
channelinside the DSL selector (count_users(purchase{channel="organic"})). track()is fire-and-forget. It returns void; the event flushes asynchronously. Don'tawaitit expecting a delivery guarantee — it's analytics, not transactional state.
Deploy this. Events start flowing. The metric definition will pick them up on the next analysis window (daily by default).
4. Put an alert on it
A metric you have to remember to look at is a metric nobody looks at. Give it a threshold and let it page:
# `--metric-id` takes the metric's id — `shipeasy metrics list` prints it
# next to the name.
shipeasy ops alerts create \
--name 'Conversion dropped' \
--metric-id <id> \
--comparator lt --threshold 0.03 \
--window-hours 24 --bucket-minutes 60Add a second metric for the thing you must not break while you ramp:
shipeasy metrics create p95_page_load_ms \
--event-name page_view \
--query 'p95(page_view, loadTimeMs)'
shipeasy ops alerts create \
--name 'Page load too slow' \
--metric-id <id> \
--comparator gt --threshold 1200 --window-hours 1A firing rule opens an item in the ops queue with the series attached, so the regression arrives as a ticket rather than as a message somebody has to notice. Full options — anomaly rules, sustained departure, required buckets — are in Alerts.
5. Read the series
shipeasy metrics series purchase_conversion prints the same buckets the dashboard charts and the
alert evaluator reads, so what you see locally is what the rule is judging:
2026-08-06 4.8%
2026-08-07 5.2%
2026-08-08 5.1%If the number is flat or missing, two checks:
- Are events actually landing? Open the Events tab and confirm non-trivial counts. Zero
means your
track()call isn't running on the path you think it is. - Is the window long enough? A 1-hour window on a low-volume event is mostly empty buckets.
Widen the window, or set
--required-bucketsso one lone sample can't page anyone.
Where to next
Aggregation types→
Conversion, count, sum, mean, ratio — what each does and when to pick it.
The metric DSL→
The full grammar — every aggregation, filter and operator, and what the parser refuses.
Threshold alerts→
Windows, buckets, anomaly rules, and the ticket a firing rule files for you.