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

# Automatic Tracking

> Heartbeat and page view tracking with privacy-first design

## Overview

Grain Analytics includes built-in automatic tracking for:

* **Heartbeat events**: Track session activity to understand user engagement
* **Page views**: Automatically detect navigation changes

Both features are **privacy-aware** and adapt their behavior based on user consent status.

## Heartbeat Tracking

Heartbeat tracking monitors user activity and sends periodic events to understand session engagement. This helps you:

* Detect active vs. inactive sessions
* Measure session duration accurately
* Identify when users are engaged with your app

### Configuration

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

const grain = createGrainAnalytics({
  tenantId: 'your-tenant-id',
  
  // Heartbeat options
  enableHeartbeat: true, // Default: true
  heartbeatActiveInterval: 120000, // 2 minutes when active (default)
  heartbeatInactiveInterval: 300000, // 5 minutes when inactive (default)
});
```

### How It Works

1. **Activity Detection**: Tracks mouse, keyboard, touch, and scroll events
2. **Smart Intervals**:
   * Active users: heartbeat every 2 minutes
   * Inactive users: heartbeat every 5 minutes
3. **Automatic**: Starts when SDK initializes, stops on destroy

### Consent-Aware Behavior

**Without Consent** (Minimal tracking):

```json theme={null}
{
  "eventName": "_grain_heartbeat",
  "userId": "ephemeral-session-id-123", // Memory-only, not persisted
  "properties": {
    "type": "heartbeat",
    "status": "active",
    "timestamp": 1704067200000,
    "_minimal": true,
    "_consent_status": "pending"
  }
}
```

**With Consent** (Full tracking):

```json theme={null}
{
  "eventName": "_grain_heartbeat",
  "userId": "user-persistent-id-456",
  "properties": {
    "type": "heartbeat",
    "status": "active",
    "timestamp": 1704067200000,
    "page": "/dashboard/settings",
    "duration": 120000,
    "event_count": 15,
    "_minimal": false,
    "_consent_status": "granted"
  }
}
```

### Disabling Heartbeat

```typescript theme={null}
const grain = createGrainAnalytics({
  tenantId: 'your-tenant-id',
  enableHeartbeat: false, // Disable heartbeat tracking
});
```

## Automatic Page View Tracking

Page view tracking automatically detects navigation changes in your application, including:

* Initial page load
* History API changes (pushState, replaceState)
* Back/forward navigation (popstate)
* Hash changes

### Configuration

```typescript theme={null}
const grain = createGrainAnalytics({
  tenantId: 'your-tenant-id',
  
  // Page view options
  enableAutoPageView: true, // Default: true
  stripQueryParams: true, // Default: true (removes ?query=params)
});
```

### Framework Support

Works automatically with:

* **React Router** (pushState-based navigation)
* **Vue Router** (History mode)
* **Next.js** (App Router and Pages Router)
* **Vanilla JS** (manual history manipulation)
* **Hash-based routing** (hashchange events)

No framework-specific configuration required!

### Query Parameter Stripping

By default, query parameters are stripped for privacy:

```typescript theme={null}
// URL: https://example.com/products?search=shoes&category=athletic

// With stripQueryParams: true (default)
{
  "page": "/products"  // Query params removed
}

// With stripQueryParams: false
{
  "page": "/products?search=shoes&category=athletic"
}
```

### Consent-Aware Behavior

**Without Consent** (Minimal tracking):

```json theme={null}
{
  "eventName": "page_view",
  "userId": "ephemeral-session-id-123",
  "properties": {
    "page": "/dashboard/settings",
    "timestamp": 1704067200000,
    "device": "Desktop",
    "browser": "Chrome",
    "os": "macOS"
  }
}
```

**With Consent** (Full tracking):

```json theme={null}
{
  "eventName": "page_view",
  "userId": "user-persistent-id-456",
  "properties": {
    "page": "/dashboard/settings",
    "timestamp": 1704067200000,
    "referrer": "https://google.com",
    "title": "Settings - Dashboard",
    "full_url": "https://example.com/dashboard/settings?tab=profile",
    "device": "Desktop",
    "browser": "Chrome",
    "os": "macOS",
    "_minimal": false,
    "_consent_status": "granted"
  }
}
```

### Disabling Auto Page View

```typescript theme={null}
const grain = createGrainAnalytics({
  tenantId: 'your-tenant-id',
  enableAutoPageView: false, // Disable automatic page views
});

// Manually track page views instead
grain.trackPageView({ page: '/custom-page' });
```

## Consent Upgrade Flow

When a user grants consent, the SDK automatically upgrades from minimal to full tracking:

1. **Before Consent**: Events use ephemeral session ID (memory-only)
2. **Consent Granted**: SDK fires `_grain_consent_granted` event with mapping
3. **After Consent**: All subsequent events use persistent user ID

```json theme={null}
{
  "eventName": "_grain_consent_granted",
  "userId": "user-persistent-id-456",
  "properties": {
    "previous_session_id": "ephemeral-session-id-123",
    "new_user_id": "user-persistent-id-456",
    "timestamp": 1704067200000
  }
}
```

This allows your backend to merge pre-consent and post-consent data if needed.

## Accessing Session Information

```typescript theme={null}
// Get current session ID (ephemeral or persistent based on consent)
const sessionId = grain.getSessionId();

// Get effective user ID (persistent if available)
const userId = grain.getEffectiveUserId();

// Get ephemeral session ID (memory-only)
const ephemeralId = grain.getEphemeralSessionId();

// Get current page path
const currentPage = grain.getCurrentPage();

// Check consent status
const hasConsent = grain.hasConsent('analytics');
```

## Privacy & Compliance

### Data Minimization

**Before Consent (Opt-in Mode - GDPR Compliant)**:

* ✅ Ephemeral session ID (memory-only JavaScript variable, not persisted anywhere)
* ✅ Page path (no query params)
* ✅ Timestamp
* ✅ Basic heartbeat events (user activity detection)
* ❌ No cookies
* ❌ No localStorage for user identifiers
* ❌ No persistent cross-session identifiers
* ❌ No personal information
* ❌ No referrer or page title

**Exceptions**: If user is explicitly identified via `identify()`/`login()` or using JWT auth, persistent identifiers are used for functional/essential purposes.

**After Consent**:

* ✅ Persistent user ID (stored in localStorage/cookie)
* ✅ Full page URL
* ✅ Referrer, page title
* ✅ Event counts, durations
* ✅ All standard tracking features

### Legitimate Interest

Minimal tracking (heartbeat, page views) can be justified under **legitimate interest** for:

* **Security**: Detect bot activity and abuse
* **Service delivery**: Ensure application functions properly
* **Analytics quality**: Basic session metrics for product improvement

Consult your legal team to ensure compliance with GDPR, CCPA, and other regulations.

### Opting Out

Users can disable tracking through consent management:

```html theme={null}
<script>
  // Revoke consent
  grain.consent.revoke();

  // After revocation, automatic tracking continues with minimal data
  // or stops entirely based on your consent configuration
</script>
```

## Best Practices

1. **Use Default Settings**: Heartbeat and page view tracking are on by default for good reason
2. **Strip Query Params**: Keep `stripQueryParams: true` to avoid tracking sensitive data
3. **Document in Privacy Policy**: Explain what data is collected before and after consent
4. **Test Consent Flow**: Verify that minimal tracking works without consent
5. **Monitor Session Metrics**: Use heartbeat data to understand engagement patterns

## Example: Complete Setup

```html theme={null}
<script src="https://tag.grainql.com/v4/your-tenant-id.js"></script>
<script>
  const grain = GrainTag.getInstance();

  // Heartbeat and page views start automatically!
  // No additional code needed.

  // When user grants consent:
  grain.consent.grant();
  // → Tracking upgrades from minimal to full automatically
</script>
```

## FAQ

**Q: Does automatic tracking work in server-side rendering (SSR)?**\
A: No, automatic tracking only works in browser environments. The SDK detects `window` and initializes tracking only on the client side.

**Q: Can I customize heartbeat intervals?**\
A: Yes, use `heartbeatActiveInterval` and `heartbeatInactiveInterval` config options.

**Q: Will automatic tracking slow down my app?**\
A: No. Event listeners are passive and debounced. Heartbeats run on intervals (not per-event). Impact is negligible.

**Q: How do I track page views in Next.js?**\
A: It works automatically! No special configuration needed for App Router or Pages Router.

**Q: Can I disable automatic tracking temporarily?**\
A: Yes, destroy the client to stop all tracking, or set `enableHeartbeat: false` and `enableAutoPageView: false` during initialization.

**Q: What happens to pre-consent events?**\
A: They're sent with an ephemeral session ID. After consent, the `_grain_consent_granted` event allows backend merging if needed.

## Related

* [Privacy & Compliance](/essentials/privacy-and-compliance)
* [Configuration](/advanced/configuration)
* [Event Tracking](/core/event-tracking)
* [Remote Config](/core/remote-config)
