# Events & screen tracking

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

Source: https://docs.grovs.io/docs/sdk/web/analytics

Requires Web SDK **2.0** or later; 1.x reported no events at all (see the [2.0 upgrade guide](https://docs.grovs.io/docs/upgrade-guides/web-2-0#3-events-now-actually-fire)).

## 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`](#tracking-screen-views) 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`](#custom-resolver), if you set one.
2. The [alias map](#screen-name-aliases).
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

- [Log purchases →](https://docs.grovs.io/docs/sdk/web/revenue)
- [Full API reference →](https://docs.grovs.io/docs/sdk/web/api-reference)
