Custom events and data-lf

Name the actions that matter, like a signup or a plan choice, with data-lf attributes or one function call. Props that look like personal data are dropped.

Events you get without code

The script sends these on its own. The $ prefix is reserved for them.

EventSent whenProps
$pageviewA page view, including History API navigations in single-page apps.None
$clickA click on an element marked with data-lf.id, your data-lf-* values
$outboundA click on a link to another site.host
$downloadA click on a file link, like a .pdf or .zip, or a link with a download attribute.ext
$scrollScroll depth at 25, 50, 75 and 90 percent. Only with data-scroll.pct
$form_startThe first interaction with a form marked data-lf-form.id
$form_submitA submit of a form marked data-lf-form.id
$form_errorA form failure you report with lf('formError', ...).id, category
$engageActive time on a page: in the foreground, with recent interaction.ms

Name clicks with data-lf

Mark the elements you care about. A click anywhere inside one sends a named event.

A named button
<button data-lf="pricing.enterprise_cta" data-lf-plan="enterprise">
  Talk to us
</button>

That click sends $click with { "id": "pricing.enterprise_cta", "plan": "enterprise" }. Dashes in attribute names become underscores, so data-lf-billing-period arrives as billing_period. Nothing else about the element is read: not its text, not its classes.

Forms

Give a form an id with data-lf-form. You get a start event on first interaction and a submit event.

A tracked form
<form data-lf-form="signup" action="/signup" method="post">
  ...
</form>

Only the id you assigned leaves the page, never a field name or value. To count failures, report them with a short category:

Report a failed submit
// After your own validation fails
lf('formError', 'signup', 'validation');

Use a code like validation or server. Never pass the error message: it can contain what the visitor typed.

Custom events

Anywhere in your code
lf('track', 'signup.start', { plan: 'pro', seats: 5 });

With the npm package, import track and call track('signup.start', { plan: 'pro', seats: 5 }).

  • Names are lowercase, start with a letter, and may use . _ : - for namespaces, up to 64 characters. The pattern is ^[a-z][a-z0-9_.:-]{0,63}$.
  • Props: up to 8 per event. Keys are lowercase snake case, up to 32 characters (^[a-z][a-z0-9_]{0,31}$).
  • Values are strings, numbers or booleans. Strings longer than 64 characters are cut to fit.
  • Anything that looks like personal data is dropped, like an email address or a long run of digits, even if you sent it on purpose.

What gets kept and what gets dropped

Props you send
{
  "plan": "pro",
  "seats": 5,
  "annual": true,
  "email": "jane@example.com",
  "phone": "+1 (555) 010-7788",
  "Plan": "Pro",
  "coupon_code": "SPRING-2026-EARLYBIRD-LAUNCH-OFFER-FOR-THE-FIRST-FIFTY-TEAMS-ONLY"
}
Props that are kept
{
  "plan": "pro",
  "seats": 5,
  "annual": true,
  "coupon_code": "SPRING-2026-EARLYBIRD-LAUNCH-OFFER-FOR-THE-FIRST-FIFTY-TEAMS-ONL"
}
Dropped keyWhy
emailLooks like personal data
phoneLooks like personal data
PlanKey is not lowercase snake case, or the value is not a string, number or boolean

Drops are counted and shown in the live install panel with their reason, so a typo never fails quietly.