Segment Data Layer Setup | OpsBlu Docs

Segment Data Layer Setup

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

Overview

A well-structured data layer is the foundation of any Segment implementation. Unlike traditional analytics platforms that rely on DOM scraping or global objects, Segment's architecture expects clean, programmatic data that conforms to a tracking plan. The data layer serves as the contract between your application and Segment, ensuring consistent event properties, user traits, and group context across all touchpoints.

Segment doesn't impose a rigid data layer structure, but following best practices ensures compatibility with Protocols (schema enforcement), simplifies destination mappings, and future-proofs your analytics infrastructure.

Data Layer Architecture Options

Option 1: Direct Segment API Calls

The simplest approach for applications with modern JavaScript frameworks:

// No intermediary data layer object
analytics.track('Product Viewed', {
  product_id: 'SKU-12345',
  name: 'Wireless Headphones',
  price: 79.99,
  currency: 'USD'
});

Pros:

  • Direct, no abstraction overhead
  • Real-time tracking
  • Framework-agnostic

Cons:

  • Couples tracking logic to business logic
  • Hard to audit without logging
  • No pre-Segment validation layer

Option 2: Centralized Data Layer Object

Expose a global data layer object that Segment reads from:

// Initialize global data layer
window.dataLayer = {
  page: {
    category: 'Electronics',
    type: 'Product Detail',
    name: 'Wireless Headphones'
  },
  user: {
    id: null,
    email_hash: null,
    login_status: 'guest',
    account_tier: null
  },
  product: null,
  cart: {
    items: [],
    total: 0,
    currency: 'USD'
  },
  consent: {
    analytics: false,
    marketing: false,
    preferences: false
  }
};

// Update as context changes
dataLayer.product = {
  product_id: 'SKU-12345',
  name: 'Wireless Headphones',
  price: 79.99,
  category: 'Electronics > Audio > Headphones'
};

// Track event from data layer
analytics.track('Product Viewed', dataLayer.product);

Pros:

  • Centralized state management
  • Easy to inspect in console
  • Can be read by multiple tools

Cons:

  • Requires synchronization discipline
  • Potential for stale data
  • Global namespace pollution

Use an event queue pattern similar to GTM's dataLayer:

// Initialize as an array
window.segmentDataLayer = window.segmentDataLayer || [];

// Helper function to push events
function pushSegmentEvent(event, properties = {}) {
  window.segmentDataLayer.push({
    event: event,
    properties: properties,
    timestamp: Date.now()
  });

  // Immediately track to Segment
  analytics.track(event, properties);
}

// Usage
pushSegmentEvent('Product Viewed', {
  product_id: 'SKU-12345',
  name: 'Wireless Headphones',
  price: 79.99
});

Pros:

  • Event history preserved
  • Supports pre-analytics.js initialization
  • Easy to add middleware (validation, enrichment)

Cons:

  • Slightly more complex setup
  • Array grows unbounded without cleanup

Required Fields by Event Type

Page Events

Every page call should include:

Field Type Required Description Example
name string Yes Human-readable page name Product Detail
category string Recommended Page type/section Electronics
url string Auto Full URL https://example.com/products/headphones
path string Auto URL path /products/headphones
title string Auto Page title Wireless Headphones - Example Store
referrer string Auto Previous page URL https://google.com/search
analytics.page('Electronics', 'Product Detail', {
  product_id: 'SKU-12345',
  product_name: 'Wireless Headphones',
  search_term: document.referrer.includes('google') ? getSearchTerm() : null
});

Identify Calls (User Traits)

Standard user traits for B2C applications:

analytics.identify('user_12345', {
  // Identity
  email: 'user@example.com',
  email_hash: sha256('user@example.com'),  // For privacy-safe destination mapping

  // Profile
  first_name: 'Jane',
  last_name: 'Doe',
  name: 'Jane Doe',
  phone: '+1-555-123-4567',

  // Demographics
  age: 32,
  birthday: '1992-03-15',
  gender: 'female',

  // Address
  address: {
    street: '123 Main St',
    city: 'San Francisco',
    state: 'CA',
    postal_code: '94102',
    country: 'US'
  },

  // Account
  created_at: '2024-01-15T10:30:00Z',
  account_tier: 'premium',
  subscription_status: 'active',
  lifetime_value: 1250.00,

  // Consent
  marketing_opt_in: true,
  sms_opt_in: false
});

Standard user traits for B2B applications:

analytics.identify('user_12345', {
  // Identity
  email: 'jane@acme.com',
  email_hash: sha256('jane@acme.com'),

  // Profile
  name: 'Jane Doe',
  title: 'VP of Marketing',
  role: 'admin',

  // Company context (also use group() call)
  company: 'Acme Corporation',
  company_id: 'comp_67890',
  company_plan: 'enterprise',
  company_mrr: 5000.00,

  // Account
  created_at: '2024-01-15T10:30:00Z',
  last_login: '2024-03-20T14:22:00Z',
  login_count: 47
});

Group Calls (Account/Organization Context)

For B2B SaaS or multi-tenant applications:

analytics.group('comp_67890', {
  // Company identity
  name: 'Acme Corporation',
  industry: 'Technology',
  employee_count: 500,
  website: 'https://acme.com',

  // Account details
  plan: 'enterprise',
  mrr: 5000.00,
  arr: 60000.00,
  created_at: '2023-06-01T00:00:00Z',

  // Usage metrics
  seats_licensed: 25,
  seats_active: 18,
  features_enabled: ['sso', 'advanced_analytics', 'api_access'],

  // Sales context
  account_owner: 'sales@example.com',
  customer_success_manager: 'csm@example.com',
  health_score: 85
});

Track Events: Ecommerce

Standardized ecommerce event properties following Segment's Ecommerce Spec:

Product Viewed

analytics.track('Product Viewed', {
  product_id: 'SKU-12345',
  sku: 'WH-BLK-001',
  category: 'Electronics',
  name: 'Wireless Headphones',
  brand: 'AudioTech',
  variant: 'Black',
  price: 79.99,
  currency: 'USD',
  quantity: 1,
  position: 3,  // Position in list
  url: 'https://example.com/products/wireless-headphones',
  image_url: 'https://cdn.example.com/images/wh-blk-001.jpg'
});

Product Added to Cart

analytics.track('Product Added', {
  cart_id: 'cart_abc123',
  product_id: 'SKU-12345',
  sku: 'WH-BLK-001',
  category: 'Electronics',
  name: 'Wireless Headphones',
  brand: 'AudioTech',
  variant: 'Black',
  price: 79.99,
  quantity: 1,
  currency: 'USD'
});

Checkout Started

analytics.track('Checkout Started', {
  order_id: 'order_xyz789',  // May not exist yet
  value: 156.96,
  revenue: 142.97,
  shipping: 5.99,
  tax: 8.00,
  discount: 10.00,
  coupon: 'SUMMER10',
  currency: 'USD',
  products: [
    {
      product_id: 'SKU-12345',
      sku: 'WH-BLK-001',
      name: 'Wireless Headphones',
      price: 79.99,
      quantity: 1,
      category: 'Electronics'
    },
    {
      product_id: 'SKU-67890',
      sku: 'BT-SPK-002',
      name: 'Bluetooth Speaker',
      price: 62.98,
      quantity: 2,
      category: 'Electronics'
    }
  ]
});

Order Completed

analytics.track('Order Completed', {
  order_id: 'ORD-20240315-001',
  checkout_id: 'checkout_abc123',
  total: 156.96,
  revenue: 142.97,  // Subtotal after discounts
  shipping: 5.99,
  tax: 8.00,
  discount: 10.00,
  coupon: 'SUMMER10',
  currency: 'USD',

  // Products array
  products: [
    {
      product_id: 'SKU-12345',
      sku: 'WH-BLK-001',
      name: 'Wireless Headphones',
      price: 79.99,
      quantity: 1,
      category: 'Electronics'
    }
  ],

  // Payment details
  payment_method: 'credit_card',
  payment_processor: 'stripe',

  // Shipping details
  shipping_method: 'ground',
  shipping_carrier: 'UPS',

  // Timestamps
  created_at: '2024-03-15T14:30:00Z',
  completed_at: '2024-03-15T14:35:22Z'
});

Track Events: Lead Generation

// Form submission
analytics.track('Form Submitted', {
  form_id: 'contact-form',
  form_name: 'Contact Sales',
  form_type: 'lead',

  // Lead source
  lead_source: 'website',
  campaign_name: 'spring-2024-promo',

  // Form fields (avoid PII unless necessary)
  company_size: '50-100',
  industry: 'Technology',
  use_case: 'Marketing Analytics'
});

// Lead created
analytics.track('Lead Created', {
  lead_id: 'lead_abc123',
  lead_score: 75,
  lead_source: 'inbound',
  lead_status: 'new',
  expected_value: 5000.00
});

Track Events: SaaS/Product Engagement

// Feature used
analytics.track('Feature Used', {
  feature_name: 'Advanced Filters',
  feature_category: 'Analytics',
  usage_count: 1,  // Lifetime or session count
  session_duration: 245  // Seconds in feature
});

// Trial started
analytics.track('Trial Started', {
  trial_plan: 'professional',
  trial_duration_days: 14,
  trial_end_date: '2024-03-29',
  source: 'homepage_cta'
});

// Subscription started
analytics.track('Subscription Started', {
  plan_id: 'plan_professional_monthly',
  plan_name: 'Professional',
  billing_cycle: 'monthly',
  amount: 99.00,
  currency: 'USD',
  trial_converted: true
});

Property Naming Conventions

Case Standards

Segment recommends snake_case for all properties:

Convention Example Use
snake_case (recommended) product_id, email_hash Universal compatibility
camelCase productId, emailHash JavaScript conventions
PascalCase ProductId, EmailHash Avoid
kebab-case product-id, email-hash Avoid (breaks dot notation)

Be consistent: Pick one convention and enforce it via Protocols.

Reserved Properties

Avoid using these reserved names as custom properties:

  • anonymousId
  • context
  • integrations
  • messageId
  • receivedAt
  • sentAt
  • timestamp
  • type
  • userId
  • version

Nested Objects vs Flat Properties

Nested (better for readability):

{
  product: {
    id: 'SKU-12345',
    name: 'Wireless Headphones',
    price: 79.99
  }
}

Flat (better for destination compatibility):

{
  product_id: 'SKU-12345',
  product_name: 'Wireless Headphones',
  product_price: 79.99
}

Recommendation: Use flat properties for maximum destination compatibility. Some destinations (especially ad platforms) don't handle nested objects well.

Context Object Enrichment

Segment automatically captures context, but you can enrich it:

Standard Context

analytics.track('Button Clicked', {
  button_text: 'Sign Up'
}, {
  context: {
    // Additional context beyond defaults
    app: {
      name: 'Example App',
      version: '2.5.1',
      build: '12345'
    },
    campaign: {
      name: 'spring-2024-promo',
      source: 'google',
      medium: 'cpc',
      term: 'analytics software',
      content: 'variant-a'
    },
    page: {
      tab_url: window.location.href,
      tab_index: getTabIndex(),
      scroll_depth: getScrollDepth()
    }
  }
});

Custom Context for Destination Mapping

analytics.track('Purchase Completed', {
  order_id: 'ORD-001',
  total: 99.99
}, {
  context: {
    // Google Ads conversion tracking
    'Google Ads': {
      conversion_id: '123456789',
      conversion_label: 'abcdef123'
    },

    // Facebook Pixel custom parameters
    'Facebook Pixel': {
      content_category: 'Electronics',
      content_ids: ['SKU-12345'],
      content_type: 'product'
    }
  }
});
// Initialize with consent state
window.consentState = {
  analytics: false,
  marketing: false,
  preferences: false,
  necessary: true  // Always true
};

// Update consent
function updateConsent(category, granted) {
  consentState[category] = granted;

  // Send to Segment for destination filtering
  analytics.track('Consent Updated', {
    consent_category: category,
    consent_granted: granted
  });

  // Enable/disable destinations based on consent
  if (!consentState.marketing) {
    analytics.integrations.disableIntegration('Facebook Pixel');
    analytics.integrations.disableIntegration('Google Ads');
  }
}

Conditional PII Handling

function identifyUser(userProfile) {
  const traits = {
    // Always safe to send
    user_id: userProfile.id,
    account_tier: userProfile.tier,
    created_at: userProfile.createdAt
  };

  // Only include PII if consent granted
  if (consentState.analytics) {
    traits.email_hash = sha256(userProfile.email);
    traits.phone_hash = sha256(userProfile.phone);
  }

  // Only include direct identifiers if marketing consent granted
  if (consentState.marketing) {
    traits.email = userProfile.email;
    traits.phone = userProfile.phone;
  }

  analytics.identify(userProfile.id, traits);
}

Regional Data Handling

// Detect region and adjust data collection
const region = getUserRegion();  // Returns 'EU', 'US', 'UK', etc.

const eventProperties = {
  product_id: 'SKU-12345',
  product_name: 'Wireless Headphones',
  price: 79.99
};

// Add region-specific context
if (region === 'EU') {
  eventProperties.gdpr_applies = true;
  eventProperties.consent_string = getConsentString();
}

analytics.track('Product Viewed', eventProperties, {
  context: {
    ip: region === 'EU' ? null : undefined  // Strip IP for EU
  }
});

Protocols Integration

Tracking Plan Enforcement

Segment Protocols enforces schema validation before events reach destinations.

Define Tracking Plan

{
  "events": {
    "Product Viewed": {
      "description": "User viewed a product detail page",
      "properties": {
        "product_id": {
          "type": "string",
          "required": true,
          "pattern": "^SKU-[0-9]{5}$"
        },
        "name": {
          "type": "string",
          "required": true
        },
        "price": {
          "type": "number",
          "required": true,
          "minimum": 0
        },
        "currency": {
          "type": "string",
          "required": true,
          "enum": ["USD", "EUR", "GBP"]
        }
      }
    }
  }
}

Violations Handling

Configure Protocols to:

  • Block: Reject events that violate schema (recommended for production)
  • Allow: Send events but flag violations (recommended for development)
  • Omit properties: Remove violating properties, send the rest

Schema Versioning

// Include schema version in events
analytics.track('Order Completed', {
  schema_version: '2.1.0',  // Track which schema version
  order_id: 'ORD-001',
  // ... rest of properties
});

Maintain a changelog:

Version Date Changes
2.1.0 2024-03-15 Added shipping_carrier property
2.0.0 2024-02-01 Renamed total to value for GA4 alignment
1.5.0 2024-01-10 Added products array to checkout events

Data Quality Validation

Pre-Send Validation

// Validation middleware
function validateEvent(event, properties) {
  const errors = [];

  // Check required properties
  if (event === 'Product Viewed') {
    if (!properties.product_id) {
      errors.push('product_id is required');
    }
    if (typeof properties.price !== 'number') {
      errors.push('price must be a number');
    }
    if (properties.currency && !['USD', 'EUR', 'GBP'].includes(properties.currency)) {
      errors.push('invalid currency code');
    }
  }

  // Log validation errors
  if (errors.length > 0) {
    console.error('Event validation failed:', event, errors);

    // Optionally track validation failure
    analytics.track('Event Validation Failed', {
      event_name: event,
      errors: errors
    });

    return false;
  }

  return true;
}

// Wrapper function
function trackEvent(event, properties) {
  if (validateEvent(event, properties)) {
    analytics.track(event, properties);
  }
}

Automated Testing

// Jest test for event structure
describe('Product Viewed Event', () => {
  it('includes required properties', () => {
    const eventData = getProductViewedEvent('SKU-12345');

    expect(eventData).toHaveProperty('product_id');
    expect(eventData).toHaveProperty('name');
    expect(eventData).toHaveProperty('price');
    expect(eventData.currency).toBe('USD');
    expect(typeof eventData.price).toBe('number');
  });

  it('matches tracking plan schema', () => {
    const eventData = getProductViewedEvent('SKU-12345');
    const schema = require('./tracking-plan-schema.json');

    const valid = validateAgainstSchema(eventData, schema['Product Viewed']);
    expect(valid).toBe(true);
  });
});

Destination-Specific Considerations

Google Analytics 4 Item Schema

GA4 expects items array with specific property names:

analytics.track('Product Added', {
  // Standard Segment properties
  product_id: 'SKU-12345',
  name: 'Wireless Headphones',
  price: 79.99,

  // GA4-specific mapping (handled by Segment destination)
  // Ensure these properties exist for best GA4 compatibility:
  item_id: 'SKU-12345',      // Maps to GA4 item_id
  item_name: 'Wireless Headphones',  // Maps to GA4 item_name
  item_category: 'Electronics',      // Maps to GA4 item_category
  quantity: 1
});

Mixpanel Super Properties

Set persistent properties across all events:

// Client-side super properties
analytics.ready(function() {
  // These properties are sent with every event to Mixpanel
  analytics.track('Super Properties Set', {
    app_version: '2.5.1',
    user_tier: 'premium',
    ab_test_variant: 'variant-b'
  });
});

// Or use Mixpanel destination settings in Segment to map user traits as super properties
analytics.identify('user_12345', {
  account_tier: 'premium',  // Automatically becomes super property in Mixpanel
  signup_date: '2024-01-15'
});

Amplitude User Properties

Use identify calls for user properties that affect all events:

analytics.identify('user_12345', {
  // These become Amplitude user properties
  account_tier: 'premium',
  total_purchases: 5,
  last_purchase_date: '2024-03-15',
  favorite_category: 'Electronics'
});

Validation and Debugging

Segment Debugger

  1. Open Segment source debugger: app.segment.com/[workspace]/sources/[source]/debugger
  2. Trigger an event on your site/app
  3. Verify event appears in Live Debugger within 10 seconds
  4. Click event to inspect payload:
    • Event name
    • Properties
    • Context
    • Destinations receiving the event

Browser Console Inspection

// Enable Segment debug mode
analytics.debug(true);

// Inspect current user
console.log('User ID:', analytics.user().id());
console.log('Anonymous ID:', analytics.user().anonymousId());
console.log('User Traits:', analytics.user().traits());

// Monitor all Segment events
analytics.on('track', function(event, properties, options) {
  console.log('Track Event:', event, properties);
});

analytics.on('identify', function(userId, traits, options) {
  console.log('Identify Call:', userId, traits);
});

analytics.on('page', function(category, name, properties, options) {
  console.log('Page Call:', category, name, properties);
});

Event Delivery Validation

  1. Go to Source > Event Delivery in Segment workspace
  2. Filter by event name: Product Viewed
  3. Check success/failure rates per destination
  4. Click into failed events to see error messages
  5. Verify retry attempts and final status

Network Tab Verification

1. Open DevTools > Network
2. Filter: api.segment.io/v1
3. Find POST request to /track, /identify, or /page
4. Inspect Request Payload:
   {
     "anonymousId": "abc-123",
     "event": "Product Viewed",
     "properties": {
       "product_id": "SKU-12345",
       "price": 79.99
     },
     "context": { ... },
     "timestamp": "2024-03-15T14:30:00.000Z"
   }
5. Verify Response: 200 OK

Troubleshooting

Symptom Likely Cause Solution
Events not appearing in debugger Analytics.js not loaded Check network tab for script load errors
Properties missing in destination Destination mapping incorrect Review destination settings and field mappings
Protocols violations Event doesn't match tracking plan Update event properties or tracking plan schema
Inconsistent property names Multiple developers, no standards Implement Protocols enforcement and code reviews
Nested objects not reaching destination Destination doesn't support nesting Flatten properties before sending
Currency showing as number Wrong data type Send currency as string: "USD" not USD
anonymousId changing frequently Cookie/localStorage cleared Investigate browser privacy settings or ad blockers
Identify traits not persisting Called after track events Call identify() before track() on page load
Group context missing from events Group call not made Ensure group() is called after identify()
Consent state not respected Destination filters not configured Set up destination filters based on consent properties

Best Practices Checklist

  • Use snake_case for all property names consistently
  • Document all events and properties in a tracking plan
  • Enforce schema with Segment Protocols in production
  • Include currency as a string (ISO 4217 code) for all monetary values
  • Flatten nested objects for maximum destination compatibility
  • Call identify() before track() on authenticated pages
  • Include schema_version property for breaking changes
  • Validate events client-side before sending to Segment
  • Test events in debugger before deploying to production
  • Set up alerts for Protocols violations and delivery failures
  • Review Event Delivery dashboard weekly for destination-specific issues
  • Maintain a data dictionary with business definitions for each property
  • Use consistent units (e.g., prices always in base currency, not cents)
  • Hash PII (email, phone) when possible for privacy-safe destination mapping
  • Include consent state in context for GDPR/CCPA compliance