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
Option 3: Event-Driven Data Layer (Recommended)
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:
anonymousIdcontextintegrationsmessageIdreceivedAtsentAttimestamptypeuserIdversion
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'
}
}
});
Consent and Privacy
Consent State Management
// 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
- Open Segment source debugger:
app.segment.com/[workspace]/sources/[source]/debugger - Trigger an event on your site/app
- Verify event appears in Live Debugger within 10 seconds
- 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
- Go to Source > Event Delivery in Segment workspace
- Filter by event name:
Product Viewed - Check success/failure rates per destination
- Click into failed events to see error messages
- 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
currencyas a string (ISO 4217 code) for all monetary values - Flatten nested objects for maximum destination compatibility
- Call
identify()beforetrack()on authenticated pages - Include
schema_versionproperty 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