Pirsch Data Layer | OpsBlu Docs

Pirsch Data Layer

Set up the data layer for Pirsch — structure events, define variables, and ensure consistent tracking data.

Overview

While traditional analytics platforms rely on complex data layer implementations with global JavaScript objects, Pirsch takes a simplified, privacy-first approach to passing custom data. Pirsch's data layer consists of event metadata that enriches your analytics while maintaining user privacy and GDPR compliance.

The data layer in Pirsch allows you to attach contextual information to events without creating persistent user profiles or violating privacy regulations. This approach gives you the insights you need while respecting user privacy by design.

Understanding Pirsch's Data Layer Philosophy

Privacy-First Architecture

Unlike conventional data layers that store user information globally:

  • No Global Data Object: Pirsch doesn't require a window-level data layer
  • Event-Scoped Data: Metadata is attached only to specific events
  • No User Profiles: Data isn't used to build persistent user identities
  • GDPR Compliant: No personally identifiable information is stored
  • Session Privacy: Sessions are aggregated, not individually tracked

Data Layer vs. Traditional Analytics

Traditional Analytics (Google Analytics):

// Global data layer
window.dataLayer = window.dataLayer || [];
dataLayer.push({
  userId: '12345',
  userEmail: 'user@example.com',
  previousPurchases: 5
});

Pirsch (Privacy-First):

// Event-specific metadata
pirsch('Purchase', {
  meta: {
    product: 'Pro Plan',
    value: 99,
    category: 'Subscriptions'
  }
});

Basic Data Layer Implementation

Simple Event Metadata

Attach basic contextual information to events:

pirsch('Button Click', {
  meta: {
    button_name: 'Subscribe',
    location: 'Header',
    color: 'Blue'
  }
});

Multiple Metadata Fields

Include comprehensive context:

pirsch('Form Submission', {
  meta: {
    form_name: 'Contact Form',
    form_location: 'Footer',
    fields_completed: 5,
    submission_time: new Date().toISOString(),
    referrer_source: document.referrer
  }
});

Dynamic Metadata Values

Calculate values at runtime:

const scrollDepth = Math.round((window.scrollY / document.body.scrollHeight) * 100);

pirsch('Page Engagement', {
  meta: {
    scroll_depth: scrollDepth + '%',
    time_on_page: performance.now(),
    viewport_width: window.innerWidth,
    device_type: window.innerWidth > 768 ? 'desktop' : 'mobile'
  }
});

E-commerce Data Layer

Product Information

Track product data without user identification:

function trackProductView(product) {
  pirsch('Product View', {
    meta: {
      product_id: product.id,
      product_name: product.name,
      product_price: product.price,
      product_category: product.category,
      product_brand: product.brand,
      product_variant: product.variant,
      in_stock: product.inStock,
      discount_applied: product.hasDiscount
    }
  });
}

Shopping Cart Events

function trackAddToCart(item, cart) {
  pirsch('Add to Cart', {
    meta: {
      product_id: item.id,
      product_name: item.name,
      product_price: item.price,
      quantity: item.quantity,
      cart_total: cart.total,
      cart_items_count: cart.itemCount,
      currency: 'USD'
    }
  });
}

Purchase Tracking

function trackPurchase(order) {
  pirsch('Purchase', {
    meta: {
      order_id: order.id,
      order_total: order.total,
      order_subtotal: order.subtotal,
      tax_amount: order.tax,
      shipping_amount: order.shipping,
      discount_amount: order.discount,
      currency: order.currency,
      items_count: order.items.length,
      payment_method: order.paymentMethod,
      shipping_method: order.shippingMethod
    }
  });
}

Page Context Data Layer

Automatic Page Context

Capture relevant page information:

function getPageContext() {
  return {
    page_path: window.location.pathname,
    page_title: document.title,
    page_url: window.location.href,
    page_referrer: document.referrer,
    page_language: document.documentElement.lang,
    viewport_size: `${window.innerWidth}x${window.innerHeight}`,
    screen_size: `${screen.width}x${screen.height}`
  };
}

// Use with events
pirsch('Page Load', {
  meta: getPageContext()
});

User Interaction Context

function trackInteraction(element, action) {
  pirsch('User Interaction', {
    meta: {
      element_type: element.tagName.toLowerCase(),
      element_id: element.id || 'no-id',
      element_class: element.className,
      element_text: element.textContent?.substring(0, 50),
      action: action,
      page_section: element.closest('[data-section]')?.dataset.section,
      timestamp: new Date().toISOString()
    }
  });
}

Form Data Layer

Form Interaction Tracking

function setupFormTracking(formElement) {
  // Track form start
  formElement.addEventListener('focusin', function(e) {
    if (e.target.matches('input, textarea, select')) {
      pirsch('Form Start', {
        meta: {
          form_id: this.id,
          form_name: this.name,
          first_field: e.target.name,
          page: window.location.pathname
        }
      });
    }
  }, { once: true });

  // Track form completion
  formElement.addEventListener('submit', function(e) {
    const formData = new FormData(this);

    pirsch('Form Submit', {
      meta: {
        form_id: this.id,
        form_name: this.name,
        fields_count: formData.keys().length,
        form_type: this.dataset.formType || 'unknown',
        page: window.location.pathname
      }
    });
  });
}

Form Validation Errors

function trackFormError(fieldName, errorMessage) {
  pirsch('Form Validation Error', {
    meta: {
      field_name: fieldName,
      error_type: errorMessage,
      form_id: event.target.closest('form')?.id
    }
  });
}

Video Player Data Layer

Video Engagement Tracking

function setupVideoTracking(videoElement) {
  const videoData = {
    video_id: videoElement.id,
    video_src: videoElement.currentSrc,
    video_duration: Math.round(videoElement.duration)
  };

  videoElement.addEventListener('play', () => {
    pirsch('Video Play', {
      meta: {
        ...videoData,
        play_position: Math.round(videoElement.currentTime)
      }
    });
  });

  videoElement.addEventListener('ended', () => {
    pirsch('Video Complete', {
      meta: {
        ...videoData,
        completion_rate: '100%'
      }
    });
  });

  // Track 25%, 50%, 75% progress
  const milestones = [25, 50, 75];
  const reached = new Set();

  videoElement.addEventListener('timeupdate', () => {
    const percent = (videoElement.currentTime / videoElement.duration) * 100;

    milestones.forEach(milestone => {
      if (percent >= milestone && !reached.has(milestone)) {
        reached.add(milestone);
        pirsch('Video Progress', {
          meta: {
            ...videoData,
            milestone: milestone + '%',
            watch_time: Math.round(videoElement.currentTime)
          }
        });
      }
    });
  });
}

Search Functionality Data Layer

Search Query Tracking

function trackSearch(query, results) {
  pirsch('Search', {
    meta: {
      search_query: query.toLowerCase(),
      search_query_length: query.length,
      results_count: results.length,
      has_results: results.length > 0,
      search_type: 'site_search',
      page: window.location.pathname
    }
  });
}

Search Result Click

function trackSearchResultClick(query, result, position) {
  pirsch('Search Result Click', {
    meta: {
      search_query: query.toLowerCase(),
      result_position: position,
      result_url: result.url,
      result_type: result.type
    }
  });
}

Framework Integration

React Context Provider

import React, { createContext, useContext } from 'react';

const AnalyticsContext = createContext();

export function AnalyticsProvider({ children }) {
  const trackEvent = (eventName, metadata = {}) => {
    if (window.pirsch) {
      window.pirsch(eventName, {
        meta: {
          ...metadata,
          framework: 'React',
          component: metadata.component || 'Unknown'
        }
      });
    }
  };

  return (
    <AnalyticsContext.Provider value={{ trackEvent }}>
      {children}
    </AnalyticsContext.Provider>
  );
}

export function useAnalytics() {
  return useContext(AnalyticsContext);
}

// Usage in component
function ProductCard({ product }) {
  const { trackEvent } = useAnalytics();

  const handleClick = () => {
    trackEvent('Product Click', {
      product_id: product.id,
      product_name: product.name,
      product_price: product.price,
      component: 'ProductCard'
    });
  };

  return <div
}

Vue.js Plugin

// analytics-plugin.js
export default {
  install(app, options) {
    app.config.globalProperties.$trackEvent = (eventName, metadata = {}) => {
      if (window.pirsch) {
        window.pirsch(eventName, {
          meta: {
            ...metadata,
            framework: 'Vue',
            app_version: options.version || '1.0.0'
          }
        });
      }
    };
  }
};

// main.js
import analyticsPlugin from './analytics-plugin';
app.use(analyticsPlugin, { version: '2.0.0' });

// Component usage
export default {
  methods: {
    handlePurchase(product) {
      this.$trackEvent('Purchase', {
        product_id: product.id,
        product_name: product.name,
        value: product.price
      });
    }
  }
};

Server-Side Data Layer

Node.js/Express Middleware

const axios = require('axios');

function pirschDataLayer(req, res, next) {
  req.trackEvent = async (eventName, metadata = {}) => {
    try {
      await axios.post('https://api.pirsch.io/api/v1/event', {
        event_name: eventName,
        event_meta: {
          ...metadata,
          server_timestamp: new Date().toISOString(),
          request_path: req.path,
          request_method: req.method
        },
        url: `${req.protocol}://${req.get('host')}${req.path}`,
        ip: req.ip,
        user_agent: req.headers['user-agent']
      }, {
        headers: {
          'Authorization': `Bearer ${process.env.PIRSCH_ACCESS_TOKEN}`,
          'Content-Type': 'application/json'
        }
      });
    } catch (error) {
      console.error('Pirsch tracking error:', error.message);
    }
  };

  next();
}

// Use middleware
app.use(pirschDataLayer);

// Track in route
app.post('/api/purchase', async (req, res) => {
  await req.trackEvent('API Purchase', {
    product_id: req.body.product_id,
    amount: req.body.amount,
    currency: req.body.currency
  });

  res.json({ success: true });
});

Data Validation and Testing

Validate Metadata Structure

function validateMetadata(metadata) {
  const issues = [];

  // Check for PII
  const piiFields = ['email', 'name', 'phone', 'address', 'userId'];
  piiFields.forEach(field => {
    if (field in metadata) {
      issues.push(`Warning: Potential PII field "${field}" detected`);
    }
  });

  // Check data types
  Object.entries(metadata).forEach(([key, value]) => {
    if (typeof value === 'object' && value !== null) {
      issues.push(`Error: Nested objects not supported for "${key}"`);
    }
  });

  return issues;
}

function trackWithValidation(eventName, metadata) {
  const issues = validateMetadata(metadata);

  if (issues.length > 0) {
    console.warn('Metadata validation issues:', issues);
  }

  pirsch(eventName, { meta: metadata });
}

Debug Mode

const DEBUG_MODE = process.env.NODE_ENV === 'development';

function trackEvent(eventName, metadata) {
  if (DEBUG_MODE) {
    console.group('Pirsch Event:', eventName);
    console.table(metadata);
    console.groupEnd();
  }

  if (window.pirsch) {
    pirsch(eventName, { meta: metadata });
  }
}

Validation and Testing

Test Data Flow

  1. Console Testing:
// Test event with metadata
pirsch('Test Event', {
  meta: {
    test_field: 'test_value',
    timestamp: new Date().toISOString()
  }
});
  1. Network Monitoring:

    • Open DevTools Network tab
    • Filter by "pirsch.io"
    • Trigger events
    • Inspect request payload to verify metadata
  2. Dashboard Verification:

    • Wait 5-10 minutes for processing
    • Check Events section in Pirsch dashboard
    • Click on event to view metadata
    • Verify all fields appear correctly

Troubleshooting

Issue Cause Solution
Metadata not appearing Incorrect structure Ensure metadata is within meta: {} object
Nested objects not showing Unsupported data type Flatten nested objects into key-value pairs
Numbers as strings Type coercion Ensure numeric values are numbers, not strings
Unicode characters broken Encoding issue Use UTF-8 encoding for special characters
Metadata truncated Value too long Keep string values under 1000 characters
PII accidentally tracked Lack of validation Implement validation to catch PII fields
Null values not tracked Filtering behavior Use empty strings or '0' instead of null
Timestamps inconsistent Timezone differences Use ISO 8601 format with timezone

Best Practices

  1. Keep It Simple: Use flat key-value pairs, avoid nested objects
  2. Consistent Naming: Use snake_case for metadata keys (e.g., product_id, not productId)
  3. No PII: Never include personally identifiable information
  4. Meaningful Keys: Use descriptive, self-documenting field names
  5. Type Consistency: Keep data types consistent (strings vs numbers)
  6. Reasonable Size: Limit metadata to essential information
  7. Validate Data: Check for PII and data type issues before tracking
  8. Document Schema: Maintain documentation of your metadata structure

Privacy Considerations

What NOT to Track

Never include in metadata:

  • Email addresses
  • Names
  • Phone numbers
  • Addresses
  • User IDs or account numbers
  • IP addresses
  • Credit card information
  • Any personally identifiable information

What to Track

Safe to include:

  • Product IDs and categories
  • Transaction amounts (without user identification)
  • Page paths and titles
  • Interaction types
  • Timestamps
  • Aggregate counts
  • Anonymous session data