> ## 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.

# Event Tracking

> Learn how to track user actions and understand event fundamentals

## What Are Events?

Events represent actions that happen in your application. Every time a user clicks a button, views a page, or completes a purchase, you can track it as an event.

Think of events as a timeline of what users do in your app. By tracking these actions, you can:

* Understand which features users engage with most
* Identify where users drop off in your funnel
* Measure the impact of changes to your product

## Basic Event Tracking

The simplest way to track an event is with a name and properties:

```typescript theme={null}
// Using @grainql/tag (recommended for browser)
import { track } from '@grainql/tag';

track('button_clicked', {
  button_name: 'signup',
  page: '/home'
});
```

**Anatomy of an event**:

* **Event name** (`button_clicked`): What happened
* **Properties** (`button_name`, `page`): Context about the event

Event names should be clear and descriptive. Use past tense to indicate an action that already happened.

<Tip>
  **Want a systematic approach to naming?** Our [Event Naming Convention](/core/event-naming) guide provides a hierarchical structure that makes events consistent and easy to query.
</Tip>

## Event Properties

Properties add context to events. They answer questions like "which button?" or "how much?":

```typescript theme={null}
import { track } from '@grainql/tag';

// Good: Rich context
track('product_viewed', {
  product_id: 'abc123',
  product_name: 'Blue Sneakers',
  category: 'footwear',
  price: 89.99
});

// Bad: Too vague
track('view');
```

**Best practices**:

* Include relevant context (IDs, names, amounts)
* Use consistent naming (snake\_case or camelCase)
* Avoid sensitive data (passwords, credit cards)
* Keep property names simple and clear

## Automatic Batching

Here's where Grain saves you effort: events are automatically batched for efficiency.

**What is batching?** Instead of sending each event immediately (slow and expensive), Grain collects events and sends them in groups.

```typescript theme={null}
import { track } from '@grainql/tag';

// These three events...
track('page_viewed', { page: '/products' });
track('button_clicked', { button: 'filter' });
track('search_performed', { query: 'shoes' });

// ...are batched and sent together
// after 5 seconds or when 50 events accumulate
```

**Default behavior**:

* Events batch every **5 seconds**
* Or when **50 events** accumulate
* Whichever comes first

You can customize this:

```typescript theme={null}
import { init } from '@grainql/tag';

init({
  tenantId: 'your-tenant-id',
  batchSize: 100,        // Send after 100 events
  flushInterval: 10000   // Or every 10 seconds
});
```

## Manual Flushing

Sometimes you need to send events immediately:

```typescript theme={null}
import { track, getInstance } from '@grainql/tag';

// Track a critical event
track('purchase_completed', {
  order_id: '12345',
  total: 99.99
});

// Force send immediately
const grain = getInstance();
await grain?.flush();
```

**When to flush manually**:

* Critical events (purchases, signups)
* Before app closes or logs out
* In serverless functions

**One-time flush**: Pass `flush: true` to track():

```typescript theme={null}
import { track } from '@grainql/tag';

// This event sends immediately
await track('checkout_started', {
  cart_total: 149.99
}, { flush: true });
```

## User Association

Events are more valuable when you know *who* performed them. Use `identify()` to associate events with a specific user:

```typescript theme={null}
import { identify, track } from '@grainql/tag';

// When user logs in
identify('user_123');

// All subsequent events include this user ID
track('feature_used', { feature: 'export' });
track('settings_changed', { setting: 'theme' });
```

Without a user ID, events are anonymous. This is fine for public websites, but for authenticated apps, always identify the user.

## Event Lifecycle

Understanding what happens to events helps you track effectively:

1. **Track**: You call `track()`
2. **Queue**: Event added to internal queue
3. **Batch**: Queue accumulates until batch size or interval
4. **Send**: Batch sent to Grain API
5. **Retry**: If failed, retry with exponential backoff
6. **Success**: Event stored in Grain analytics

```typescript theme={null}
import { init } from '@grainql/tag';

// Configure retry behavior
init({
  tenantId: 'your-tenant-id',
  retryAttempts: 3  // Try 3 times if failed
});
```

## Reliable Delivery

Grain Tag ensures events are not lost, even when:

* Network is slow or fails temporarily
* User closes the browser
* App crashes

**Automatic page exit handling**: Grain Tag automatically listens for `beforeunload`, `pagehide`, and `visibilitychange` events and uses the Beacon API to flush pending events. You do not need to add your own event listeners for page exit -- this is handled for you out of the box.

## Common Patterns

### Page View Tracking

Grain Tag auto-tracks page views (including SPA navigations via the History API), so you typically do not need to track these manually. If you need custom page view events:

```typescript theme={null}
import { track } from '@grainql/tag';

track('page_viewed', {
  page: window.location.pathname,
  title: document.title,
  referrer: document.referrer
});
```

### Button Click Tracking

```typescript theme={null}
import { track } from '@grainql/tag';

button.addEventListener('click', () => {
  track('button_clicked', {
    button_name: button.id,
    button_text: button.textContent,
    page: window.location.pathname
  });
});
```

### Form Submission

```typescript theme={null}
import { track } from '@grainql/tag';

form.addEventListener('submit', async (e) => {
  // Track before submission
  await track('form_submitted', {
    form_name: form.id,
    num_fields: form.elements.length
  }, { flush: true });

  // Continue with form submission
});
```

## Debug Mode

Not sure if events are sending? Enable debug mode in two ways:

**Via URL parameter** -- add `?grain_debug=1` to any page URL. This enables debug logging in the browser console without changing code.

**Via config**:

```typescript theme={null}
import { init } from '@grainql/tag';

init({
  tenantId: 'your-tenant-id',
  debug: true
});
```

You'll see logs for queueing, batching, sending, and responses.

## Next Steps

<CardGroup cols={2}>
  <Card title="User Identification" icon="user" href="/core/user-identification">
    Learn to track users across sessions
  </Card>

  <Card title="User Properties" icon="tags" href="/core/user-properties">
    Add attributes to user profiles
  </Card>

  <Card title="Template Events" icon="sparkles" href="/core/template-events">
    Use pre-built event methods
  </Card>

  <Card title="API Reference" icon="book" href="/api-reference/core-methods">
    See all tracking methods
  </Card>
</CardGroup>

<Note>
  View your events in real-time at [grainql.com/dashboard](https://grainql.com/dashboard)
</Note>

<Note>
  These examples use `@grainql/tag`. If you're using `@grainql/analytics-web`, replace imports with `import { createGrainAnalytics } from '@grainql/analytics-web'` and use `grain.track()` on the client instance.
</Note>
