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
- Console Testing:
// Test event with metadata
pirsch('Test Event', {
meta: {
test_field: 'test_value',
timestamp: new Date().toISOString()
}
});
Network Monitoring:
- Open DevTools Network tab
- Filter by "pirsch.io"
- Trigger events
- Inspect request payload to verify metadata
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
- Keep It Simple: Use flat key-value pairs, avoid nested objects
- Consistent Naming: Use snake_case for metadata keys (e.g.,
product_id, notproductId) - No PII: Never include personally identifiable information
- Meaningful Keys: Use descriptive, self-documenting field names
- Type Consistency: Keep data types consistent (strings vs numbers)
- Reasonable Size: Limit metadata to essential information
- Validate Data: Check for PII and data type issues before tracking
- 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