PLG event catalog
Apex ships with a curated catalog of eight PLG lifecycle events. Emit any subset of them from your product via the Apex SDK and Apex derives activation, expansion, and retention rollups without bespoke queries.
Using the catalog is optional — apex.track() accepts any event name — but the catalog is the path of least resistance for product-led teams.
Installing the SDK
npm install @apex-inc/sdk
import { init, identify, track, group, feature } from "@apex-inc/sdk";
import { EVENTS, type ActivatedEvent } from "@apex-inc/sdk/plg-events";
init({
workspaceKey: "<your-workspace-key>",
apiUrl: "https://app.apex.inc",
});
The eight events
signup
A user created an account. Fire once per user lifecycle.
import { EVENTS, type SignupEvent } from "@apex-inc/sdk/plg-events";
apex.track(EVENTS.SIGNUP, {
method: "google",
referralSource: "product-hunt",
} satisfies SignupEvent);
activated
User hit your product's core activation milestone. You define the milestone. Fire once per user.
import { EVENTS, type ActivatedEvent } from "@apex-inc/sdk/plg-events";
apex.track(EVENTS.ACTIVATED, {
daysToActivation: 2,
milestone: "first-experiment-shipped",
} satisfies ActivatedEvent);
feature_first_use
User used a named feature for the first time. Emit on every feature, only on first use. Use apex.feature() for recurring usage.
apex.track(EVENTS.FEATURE_FIRST_USE, {
featureKey: "cohort-builder",
daysFromSignup: 7,
});
aha_moment
The user saw the product's value. Often emitted after a specific sequence ("connected a source and ran their first experiment"). Definition is product-specific.
apex.track(EVENTS.AHA_MOMENT, {
label: "first-winning-experiment",
daysFromSignup: 14,
confidence: 0.92,
});
expansion
Paying account increased spend (upgrade, more seats, more usage). Amount in positive units.
apex.track(EVENTS.EXPANSION, {
amount: 300,
currency: "USD",
driver: "plan_upgrade",
fromPlan: "growth",
toPlan: "scale",
});
contraction
Paying account decreased spend without churning. Amount in positive units (representing the reduction).
apex.track(EVENTS.CONTRACTION, {
amount: 200,
currency: "USD",
driver: "seat_removed",
});
reactivation
A churned account came back. Fire once on re-signup or re-activation.
apex.track(EVENTS.REACTIVATION, {
daysInactive: 180,
reason: "feature-launch-email",
});
churn
Account cancelled or let subscription lapse. Fire once on the churn transition.
apex.track(EVENTS.CHURN, {
reason: "switched-vendor",
cancelType: "voluntary",
daysActive: 410,
});
Typed helper
If you prefer strict typing over string-name + object, use trackPlgEvent:
import { trackPlgEvent, EVENTS } from "@apex-inc/sdk/plg-events";
import * as apex from "@apex-inc/sdk";
trackPlgEvent(apex, EVENTS.ACTIVATED, {
daysToActivation: 3,
milestone: "first-campaign-sent",
});
trackPlgEvent<K> infers the payload shape from the event name so you get compile-time errors if you pass the wrong fields.
Groups and features
For B2B products that want account-level rollups:
apex.identify("chris@acme.com", { name: "Chris", plan: "growth" });
apex.group("acct_acme", { plan: "growth", arr: 120000 });
apex.feature("cohort-builder", { variant: "new-editor", enabled: true });
Every track and feature call after apex.group() stamps the active groupId so PQA (product-qualified account) analyses are a single query away.
What Apex does with these
- Activation rate rollup on the org dashboard —
activatedevents /signupevents over time. - Time to aha — median
daysFromSignuponaha_momentevents. - Feature adoption —
feature_first_usecounts perfeatureKey. - Expansion MRR — sum of
expansion.amountbucketed by month. - Churn rate — rolling 30-day
churncount / active customer count. - PQL/PQA scoring — event combinations feed the built-in scoring engine.
Related
- SDK reference — full
@apex-inc/sdksurface. - Scoring — how events become PQL/PQA scores.
- Archive, never delete — what happens to event definitions you stop using.