// context projectglobal

Custom Events

Custom events are any user action you care about beyond a pageview — clicked a CTA, submitted a form, completed a signup, watched 30s of a video, hit an error in your app, etc.

The SDK offers two ways to record them. Pick whichever fits — they produce identical wire payloads.

1. Loguro.track() — from JavaScript

Best for events that need runtime data: the active cart total, the user’s plan, the experiment variant they’re in.

window.Loguro.track('signup_completed', {
  plan: 'pro',
  amount_cents: 4900,
  source: 'pricing-page',
  experiment_variant: 'B'
});

Requires data-analytics to include events (or all). If analytics is off or events is not enabled, the call is a no-op.

Constraints

  • Event name ≤ 64 characters. Longer names are truncated with a console.warn.
  • custom_props serialized JSON ≤ 4 KB. Larger payloads are dropped entirely with a console.warn — split the data or store the heavy parts server-side.
  • Non-string names (empty, undefined, numbers) are rejected with a warning.

Naming convention

Stick to snake_case, past tense:

✅ Good❌ Avoid
signup_completedSignup Completed (spaces, casing)
cta_clickedcta-click (mixed style)
video_30s_watcheduserJustWatched30Seconds... (verbose)

Names live forever in your analytics history. Plan them like you’d plan column names in a database.

2. [data-loguro-event] — from HTML

Best for marketing pages where non-engineers add tracking. Add the attribute, the SDK does the rest.

<button
  data-loguro-event="cta_clicked"
  data-loguro-cta="hero"
  data-loguro-page="pricing"
>
  Start free trial
</button>

When clicked, this fires a custom event:

{
  "event_type": "custom",
  "custom_event_name": "cta_clicked",
  "custom_props": { "cta": "hero", "page": "pricing" }
}

Every data-loguro-* attribute (except data-loguro-event itself) is included in custom_props, with the data-loguro- prefix stripped.

Requires data-analytics to include auto-events (or all).

What gets captured

  • Click — on the element or anything inside it. The SDK walks up the DOM tree to find the nearest ancestor with [data-loguro-event].
  • Form submit — same bubbling logic; put the attribute on <form> to capture submissions.

Click-vs-submit example

<form data-loguro-event="signup_form_submit" data-loguro-step="2">
  <input name="email" />
  <button>Continue</button>
</form>

Clicking the button fires nothing on its own (no data-loguro-event on it). The submit handler on the form fires signup_form_submit with { step: "2" }.

If you want both click and submit tracking, put the attribute on the button too with a different event name.

Wire shape (both methods)

Whether triggered via JS or DOM, the event lands in the batch buffer as:

{
  "event_type": "custom",
  "custom_event_name": "<your name>",
  "url": "https://example.com/page",
  "path": "/page",
  "visitor_id": "01HZ...",
  "session_id": "01HZ...",
  "user_id": "user_42",
  "timestamp": "2026-05-14T10:00:00.000Z",
  "custom_props": { /* whatever you passed */ }
}

user_id only appears if Loguro.identify() has been called (see Identity).

Why event_type: "custom" and not the event name directly?

Stable schema. The Parquet storage on our side uses a bloom filter on event_type sized for a handful of distinct values (pageview, custom, click, etc.). If we used your event name as event_type, every product launching new events would explode the cardinality and break the bloom — making GROUP BY event_type dashboards slow and WHERE event_type = "pageview" queries inaccurate.

So the schema is: event_type stays low-cardinality (~8 values), and your unique event name lives in custom_event_name (top-level field, dictionary-encoded for compression).

Disabling at runtime

window.Loguro.setEnabled(false);   // all subsequent track() / pageview() calls are no-ops
window.Loguro.setEnabled(true);    // re-enable

Useful for cookie-consent flows: load the script normally, but call setEnabled(false) until the user accepts. See Privacy.

Next

// related

See also