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.
| Event | Sent when | Props |
|---|---|---|
$pageview | A page view, including History API navigations in single-page apps. | None |
$click | A click on an element marked with data-lf. | id, your data-lf-* values |
$outbound | A click on a link to another site. | host |
$download | A click on a file link, like a .pdf or .zip, or a link with a download attribute. | ext |
$scroll | Scroll depth at 25, 50, 75 and 90 percent. Only with data-scroll. | pct |
$form_start | The first interaction with a form marked data-lf-form. | id |
$form_submit | A submit of a form marked data-lf-form. | id |
$form_error | A form failure you report with lf('formError', ...). | id, category |
$engage | Active 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.
<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.
<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:
// 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
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
{
"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"
}{
"plan": "pro",
"seats": 5,
"annual": true,
"coupon_code": "SPRING-2026-EARLYBIRD-LAUNCH-OFFER-FOR-THE-FIRST-FIFTY-TEAMS-ONL"
}| Dropped key | Why |
|---|---|
email | Looks like personal data |
phone | Looks like personal data |
Plan | Key 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.