Skip to main content

Overview

All events you track with Grain are automatically sent to your analytics dashboard at grainql.com/dashboard. This guide shows you how to structure your events effectively to get the most actionable insights.
Custom Analytics UIs: Build custom analytics dashboards using the Query API. The Query API allows you to programmatically access your analytics data and create tailored experiences.

Accessing Your Analytics

After tracking events with the SDK, view them in your dashboard:
  1. Go to grainql.com/dashboard
  2. View real-time event streams
  3. Analyze event properties and patterns
  4. Filter by user IDs and properties
  5. Build custom reports and visualizations
  6. Export data for further analysis

Use Descriptive Event Names

Clear, specific event names make your analytics easier to understand:
// ✅ Good: Clear and specific
grain.track('checkout_completed', { orderId: '123', total: 99.99 });
grain.track('video_playback_started', { videoId: 'abc', duration: 120 });
grain.track('signup_form_submitted', { method: 'email' });

// ❌ Bad: Vague and generic
grain.track('action', { type: 'checkout' });
grain.track('event', { name: 'video' });
grain.track('click', { button: 'signup' });
Use underscores (_) for consistency and readability.

Include Rich Context

Add relevant properties to every event for deeper analysis:
grain.track('product_viewed', {
  product_id: 'prod_123',
  product_name: 'Blue Sneakers',
  category: 'footwear',
  price: 89.99,
  currency: 'USD',
  source: 'search_results',
  referrer: document.referrer
});
The more context you provide, the better insights you’ll get from your dashboard.

Use Template Events

Grain provides pre-built template events for common scenarios:
// Login tracking with standard properties
await grain.trackLogin({
  method: 'email',
  success: true,
  twoFactorEnabled: false
});

// Purchase tracking with item details
await grain.trackPurchase({
  orderId: 'order_789',
  total: 149.99,
  currency: 'USD',
  items: [
    { id: 'item_1', name: 'Product A', price: 99.99, quantity: 1 },
    { id: 'item_2', name: 'Product B', price: 50.00, quantity: 1 }
  ],
  paymentMethod: 'credit_card'
});

// Add to cart tracking
await grain.trackAddToCart({
  itemId: 'prod_123',
  itemName: 'Blue Sneakers',
  price: 89.99,
  quantity: 1,
  category: 'footwear'
});
These template events ensure consistent tracking across your app.

Track User Properties

Use properties to segment users in your dashboard:
// Set user attributes
await grain.setProperty({
  plan: 'premium',
  signup_date: '2024-01-15',
  total_purchases: '5'
});

// Properties let you filter and group users
grain.track('feature_used', { feature: 'export' });
You can set up to 4 properties per request. All values are converted to strings automatically.

Consistent Naming Conventions

Use the same property names across related events:
// ✅ Good: Consistent property names
grain.track('video_started', {
  video_id: 'abc',
  video_title: 'Tutorial',
  duration_seconds: 120
});

grain.track('video_paused', {
  video_id: 'abc',
  video_title: 'Tutorial',
  duration_seconds: 120,
  current_time: 45
});

grain.track('video_completed', {
  video_id: 'abc',
  video_title: 'Tutorial',
  duration_seconds: 120
});

// ❌ Bad: Inconsistent naming
grain.track('video_started', { videoId: 'abc', length: 120 });
grain.track('video_paused', { video_id: 'abc', duration: 120 });
grain.track('video_completed', { id: 'abc', time: 120 });
Consistent naming makes it easier to analyze event sequences in your dashboard.

Track User Journeys

Structure events to understand user flows:
// Track key steps in a conversion funnel
grain.track('signup_form_viewed');

grain.track('signup_form_started', {
  method: 'email'
});

grain.track('signup_form_submitted', {
  method: 'email'
});

await grain.trackSignup({
  method: 'email',
  source: 'landing_page',
  plan: 'free',
  success: true
});
Your dashboard can show where users drop off in multi-step processes.

Session Tracking

Track session duration for engagement analysis:
function SessionTracker() {
  const track = useTrack();
  const sessionStart = useRef(Date.now());
  const sessionId = useRef(crypto.randomUUID());
  
  useEffect(() => {
    grain.track('session_started', {
      session_id: sessionId.current,
      page: window.location.pathname
    });
    
    return () => {
      const durationSeconds = Math.floor((Date.now() - sessionStart.current) / 1000);
      grain.track('session_ended', {
        session_id: sessionId.current,
        duration_seconds: durationSeconds
      }, { flush: true });
    };
  }, [track]);
  
  return null;
}
Use flush: true on session end to ensure the event is sent before the page unloads.

Error and Exception Tracking

Track errors to identify and fix issues:
// JavaScript errors
window.addEventListener('error', (event) => {
  grain.track('javascript_error', {
    error_message: event.message,
    error_file: event.filename,
    error_line: event.lineno,
    page_url: window.location.pathname,
    user_agent: navigator.userAgent
  });
});

// Application errors
try {
  await processPayment();
} catch (error) {
  grain.track('payment_error', {
    error_type: error.name,
    error_message: error.message,
    payment_method: 'credit_card',
    amount: orderTotal
  });
  
  // Re-throw or handle
  throw error;
}

Page View Tracking

Track page navigation for traffic analysis:
// Automatic page view tracking in React
function usePageTracking() {
  const track = useTrack();
  const location = useLocation();
  
  useEffect(() => {
    grain.trackPageView({
      page: location.pathname,
      title: document.title,
      referrer: document.referrer,
      url: window.location.href
    });
  }, [location, track]);
}

// In your app
function App() {
  usePageTracking();
  
  return <Routes>{/* your routes */}</Routes>;
}

Use User Identification

Identify users to track them across sessions:
// After user logs in
grain.identify('user_123');

// Or use setUserId
grain.setUserId('user_123');

// Set user properties for segmentation
await grain.setProperty({
  email: '[email protected]',
  plan: 'premium'
});

// All subsequent events will include the user ID
grain.track('dashboard_viewed');
Before login, Grain automatically generates and persists a unique anonymous ID for each user.

Preload Remote Config

If you’re using remote config, preload it for faster access:
const grain = createGrainAnalytics({
  tenantId: 'your-tenant-id',
  defaultConfigurations: {
    feature_enabled: 'false',
    theme: 'light'
  }
});

// Preload configurations on app start
await grain.preloadConfig(
  ['feature_enabled', 'theme'], // Immediate keys
  { plan: 'premium' } // User properties for segmentation
);

// Access immediately (cache-first)
const featureEnabled = grain.getConfig('feature_enabled');
This ensures configs are available when you need them.

Batch Events Automatically

Grain automatically batches events for efficiency:
// These events are queued and sent together
grain.track('button_clicked', { button: 'cta' });
grain.track('modal_opened', { modal: 'signup' });
grain.track('form_started', { form: 'signup' });

// Events are sent when:
// 1. Batch size reaches 50 events (configurable)
// 2. 5 seconds elapse (configurable)
// 3. Page unload occurs (automatic)
// 4. You call flush() manually
For critical events, force immediate send:
await grain.track('purchase_completed', {
  orderId: '123',
  total: 99.99
}, { flush: true });

Best Practices Summary

1. Descriptive Names: Use clear, specific event names like checkout_completed 2. Rich Properties: Include relevant context in every event 3. Consistent Naming: Use the same property names across similar events 4. Template Events: Use built-in methods like trackPurchase() for consistency 5. User Properties: Set user attributes for segmentation 6. Track Funnels: Structure events to understand user journeys 7. Error Tracking: Log errors with detailed context 8. User Identification: Use identify() or setUserId() to track across sessions 9. Flush Critical Events: Use flush: true for important events like purchases 10. Preload Config: Load remote config early for faster access

Common Patterns

E-commerce Flow

grain.track('product_list_viewed', { category: 'shoes' });
grain.track('product_viewed', { product_id: 'prod_123' });
await grain.trackAddToCart({ itemId: 'prod_123', price: 89.99 });
await grain.trackCheckout({ total: 89.99, items: [...] });
await grain.trackPurchase({ orderId: 'order_789', total: 89.99 }, { flush: true });

Authentication Flow

grain.track('login_form_viewed');
await grain.trackLogin({ method: 'email', success: true });
grain.identify('user_123');
await grain.setProperty({ plan: 'free', signup_date: '2024-01-15' });

Feature Usage

grain.track('feature_discovered', { feature: 'export' });
grain.track('feature_enabled', { feature: 'export' });
grain.track('feature_used', { feature: 'export', export_format: 'csv' });

Next Steps