Docs
grovs.io Start for free

Events & screen tracking

Track custom events and screen views in your web app with the Grovs SDK.

Requires Web SDK 2.0 or later; 1.x reported no events at all (see the 2.0 upgrade guide).

Tracking custom events

track records an event with optional properties and tags:

javascript
Grovs.track("purchase", { item: "sku-42", price: 19.99 }, ["checkout"]);
Grovs.track("level_complete", { level: 5, score: 1200 });
Grovs.track("button_tap");

Every custom event carries the most recently viewed screen as screen_name, for segmenting by screen.

Event name rules

Names must be non-empty and can't be a reserved system name:

install, reinstall, app_open, view, open, time_spent, reactivation, user_referred

Since 2.1, push, in-app and journey event names are refused too: push_open, push_delivered, push_held, in_app_viewed, in_app_clicked, in_app_dismissed, in_app_answered, in_app_commented, in_app_held, journey_entered, journey_held, journey_path.

Use trackScreenView for screen views. Rejected names are dropped with a warning, visible at debugLevel: "warn" or "info".

Property values

Values can be strings, numbers, booleans, null, or nested arrays and objects of those. The SDK sanitizes each value:

  • Date, URL and BigInt become strings.
  • Values JSON can't represent (NaN, Infinity, functions, symbols, circular references) are dropped for that key only. Other properties still send.
  • Over 8 KB of UTF-8 drops all properties. The event is still recorded.
  • Sanitization stops after 10,000 values so a large object graph can't stall the page. Past that, all properties are dropped and the event is still recorded.

Tags

Up to 20 tags per event, each up to 255 characters. Global tags attach to every later event, including system events such as time_spent:

javascript
Grovs.setGlobalTags(["beta"]);
 
Grovs.setGlobalTags(null);  // clear

Tracking screen views

javascript
Grovs.trackScreenView("Checkout", { section: "payment" });

Screen names are capped at 255 characters.

Automatic screen tracking

autoTrackScreenViews defaults to true: single-page-app route changes fire screen views. The SDK observes pushState, replaceState, popstate and hashchange.

To turn it off:

javascript
await Grovs.configure({ apiKey: "your-api-key", autoTrackScreenViews: false });

How a screen name is chosen

The first rule that produces a name wins:

  1. Grovs.screenNameProvider, if you set one.
  2. The alias map.
  3. document.title.
  4. The pathname.

Resolution waits one animation frame, so document.title reflects the new page, not the one left. If your framework renders later than that, name the screen with the resolver or an alias.

Custom resolver

Grovs.screenNameProvider receives the new URL and returns a name, "suppress" to skip the navigation, or "automatic" to fall through:

javascript
Grovs.screenNameProvider = (url) => {
  if (url.pathname.startsWith("/legal")) return "suppress";
  if (url.pathname.startsWith("/product/")) return "Product";
  return "automatic";
};

Screen name aliases

Collapse high-cardinality URLs with alias patterns, or a catalogue produces one dashboard row per id:

javascript
Grovs.setScreenAliases({
  "/product/:id": "Product",
  "/docs/*": "Documentation",
  "/checkout": "Checkout",
});

When several patterns match, the most specific wins regardless of order: more literal text first, then :param over *. A bare * is a catch-all every other pattern beats. Patterns with more than two * are ignored, with a warning at debugLevel: "warn"; use :name for inner segments.

Aliases sync after authentication. A failed sync retries on the next configure or re-enable.

Delivery

Events are queued, persisted to localStorage and sent in batches of 50.

  • First batch: once configure has authenticated and resolved attribution. It includes anything an earlier visit left undelivered.
  • Then: every five seconds, immediately at 50 events, and when the page is hidden. An empty queue makes no request.
  • Retries: network errors, 429 and 5xx get up to three attempts with exponential backoff and jitter. Timeouts, other 4xx and the tab-close batch get one attempt.
  • Retry-After: honored. A delay longer than the retry cap ends retries, scheduled batches wait it out, and an explicit flush() still sends.
  • Invalid events: ones the backend rejects as permanently invalid are dropped, not retried.
  • Queue limits: 1,000 events or one million characters. Anything older than seven days is discarded.

The final batch of a visit is sent with keepalive when the page is hidden. It stays stored until acknowledged, so if the page closes first, the next load resends it. Each event carries an event_id that Grovs cloud deduplicates on. With baseURL pointing at your own backend, deduplicating on event_id is up to you.

Flushing

Drain the queue before a hard navigation:

javascript
await Grovs.flush();

With nothing in flight, flush() drains the queue, ignoring the interval and any server cooldown. If a scheduled delivery is running, it joins that one and resolves when it completes, even if a cooldown leaves events queued. Before authentication, before attribution resolves, or while consent is pending, it resolves immediately without sending.

Sessions

Sessions are shared across tabs and rotate after 30 minutes of combined idle time. Every event carries its session id.

Next steps

Last updated 2026-10-27
Was this page helpful?