> ## Documentation Index
> Fetch the complete documentation index at: https://docs.grainql.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Track an event

> The track method sends a named event with properties from the Grain Tag or Analytics Web.

An event is one thing that happened in your application: a click, a page view, a purchase. You
send an event with `track`. Both SDKs queue events, send them in batches, and retry on failure.
This page covers custom events. Events that Grain collects without code are on
[Automatic tracking](/core/automatic-tracking).

<CodeGroup>
  ```javascript Grain Tag theme={null}
  import { track } from '@grainql/tag';

  track('cart_item_added', {
    product_id: 'SKU123',
    price: 89.99
  });
  ```

  ```typescript Analytics Web theme={null}
  import { createGrainAnalytics } from '@grainql/analytics-web';

  const grain = createGrainAnalytics({ tenantId: 'your-tenant-id' });

  await grain.track('cart_item_added', {
    product_id: 'SKU123',
    price: 89.99
  });
  ```
</CodeGroup>

The first argument is the event name. The second argument is an object of properties. In the
Grain Tag, `track` returns nothing and is safe to call before `init`. The call is queued and
replayed after `init` runs. In Analytics Web, `track` returns a promise that resolves when the
event is in the queue. Read [Name events](/core/event-naming) for the naming convention.

The TypeScript types of the published packages win over this page when they disagree.

## Properties

Properties answer the questions "which one" and "how much". Grain adds device, browser, and
attribution properties to every event on its own. Read
[Automatic properties](/core/event-properties).

1. Include the identifiers and amounts that a query needs: `product_id`, `total`, `plan`.
2. Use one naming style for every property. The site uses `snake_case`.
3. Do not send passwords, card numbers, or other personal data.
4. Put data in properties, not in the event name. `product_viewed` with `product_id: 'SKU123'`,
   not `product_SKU123_viewed`.

## Batching

Both SDKs collect events in a queue and send the queue as one request. The queue flushes every
5 seconds, or when it holds 50 events, whichever comes first.

| Limit | Value | Where |
| - | - | - |
| Events per batch | 50 | `batchSize` in `GrainTagConfig` and in `GrainConfig` |
| Flush interval | 5000 ms | `flushInterval` in `GrainTagConfig` and in `GrainConfig` |
| Retry attempts | 3 | `retryAttempts` in `GrainTagConfig` and in `GrainConfig` |
| Base retry delay | 1000 ms | `retryDelay` in `GrainConfig` (Analytics Web only) |

<CodeGroup>
  ```javascript Grain Tag theme={null}
  import { init } from '@grainql/tag';

  init({
    tenantId: 'your-tenant-id',
    batchSize: 100,
    flushInterval: 10000
  });
  ```

  ```typescript Analytics Web theme={null}
  import { createGrainAnalytics } from '@grainql/analytics-web';

  const grain = createGrainAnalytics({
    tenantId: 'your-tenant-id',
    batchSize: 100,
    flushInterval: 10000
  });
  ```
</CodeGroup>

Read [Configuration options](/advanced/configuration) for every option and its default.

## Flush the queue

`flush` sends the queue now and resolves when the request is complete. Call it before a
serverless function returns, and after a purchase or a signup.

<CodeGroup>
  ```javascript Grain Tag theme={null}
  import { track, getInstance } from '@grainql/tag';

  track('checkout_completed', { order_id: '12345', total: 99.99 });

  await getInstance()?.flush();
  ```

  ```typescript Analytics Web theme={null}
  await grain.track('checkout_completed', { order_id: '12345', total: 99.99 });

  await grain.flush();
  ```
</CodeGroup>

In Analytics Web, `track` accepts a third argument of type `SendEventOptions`. When `flush` is
`true`, the SDK sends the queue as soon as the event is in it. The Grain Tag `track` has no
options argument. Call `flush` on the instance instead.

```typescript Analytics Web theme={null}
await grain.track('checkout_started', { cart_total: 149.99 }, { flush: true });
```

## Page unload

The Grain Tag listens for `beforeunload`, `pagehide`, and `visibilitychange`. When one of them
fires, it sends the queue with the Beacon API. Analytics Web does the same on `beforeunload`.
Do not add your own unload listener for Grain events.

## Delivery and retry

1. You call `track`. The SDK adds the event to the queue.
2. The queue reaches `batchSize` events, or `flushInterval` passes, or you call `flush`.
3. The SDK sends the batch to the Grain API.
4. If the request fails, the SDK retries up to `retryAttempts` times with exponential backoff.
5. When a request succeeds, the events are stored and appear in the dashboard.

Read [Errors and retries](/advanced/error-handling) for what happens after the last retry.

## Attach a user

Events without a user ID are anonymous. Call `identify` after login, and every later event
carries the user ID. Read [Identify users](/core/user-identification).

<CodeGroup>
  ```javascript Grain Tag theme={null}
  import { identify, track } from '@grainql/tag';

  identify('user_123');
  track('feature_used', { feature: 'export' });
  ```

  ```typescript Analytics Web theme={null}
  grain.identify('user_123');
  await grain.track('feature_used', { feature: 'export' });
  ```
</CodeGroup>

## Debug logging

Set `debug: true` in the configuration of either SDK. The SDK then logs queueing, batching,
requests, and responses to the browser console. For the Grain Tag, you can also add
`?grain_debug=1` to the page URL. This turns on the same logs without a code change.

## Rules for the client

1. Do not track page views by hand when automatic page views are on. Both SDKs track them,
   including History API navigation. Read [Automatic tracking](/core/automatic-tracking).
2. Do not start an event name with `_grain_`. The prefix is reserved for system events.
3. Call `flush` before a process exits. A serverless function ends before the flush timer
   fires.
4. In Analytics Web, pass `{ flush: true }` for an event that must leave the page now, such as
   a form submit that navigates away.
5. Do not retry a `track` call yourself. The SDK retries `retryAttempts` times.
6. In the Grain Tag, `import { track } from '@grainql/tag'` has no side effects. Outside a
   browser it returns a no-op, so a server render does not need a guard.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.