Amplitude Data Layer Setup | OpsBlu Docs

Amplitude Data Layer Setup

How to configure data layer variables and user properties for Amplitude. Covers data preparation, custom properties, event enrichment, and integration.

Overview

While Amplitude doesn't use a traditional data layer like Google Tag Manager, establishing a structured data architecture is essential for consistent, maintainable tracking. A well-designed data model ensures event properties are standardized, user attributes are enriched properly, and downstream analysis remains reliable as your implementation scales.

This guide covers how to prepare, structure, and govern data before sending it to Amplitude.

Data Architecture Principles

Event-Driven Design

Amplitude is built on an event-based model where each user action is captured as a discrete event with associated properties:

amplitude.track('product_viewed', {
  product_id: 'SKU-12345',
  product_name: 'Wireless Headphones',
  category: 'Electronics',
  price: 79.99,
  currency: 'USD',
  view_source: 'search_results'
});

User Properties vs. Event Properties

  • User Properties: Attributes that describe the user (plan type, signup date, region)
  • Event Properties: Contextual data specific to the event (product details, interaction type)
// Set user properties (persist across events)
amplitude.setUserProperties({
  plan: 'premium',
  signup_date: '2024-01-15',
  account_type: 'business',
  region: 'US-West'
});

// Track event with event properties (specific to this action)
amplitude.track('purchase_completed', {
  order_id: 'ORD-98765',
  total_amount: 156.99,
  item_count: 3,
  payment_method: 'credit_card'
});

Required Data Fields

Attribution Data

Capture marketing attribution on initial page load:

// Extract UTM parameters from URL
const urlParams = new URLSearchParams(window.location.search);
const utmData = {
  utm_source: urlParams.get('utm_source'),
  utm_medium: urlParams.get('utm_medium'),
  utm_campaign: urlParams.get('utm_campaign'),
  utm_content: urlParams.get('utm_content'),
  utm_term: urlParams.get('utm_term'),
  referrer: document.referrer,
  initial_referrer: document.referrer
};

// Store as user properties for attribution
amplitude.setUserProperties(utmData);

// Also attach to first event
amplitude.track('page_viewed', {
  ...utmData,
  page_path: window.location.pathname,
  page_title: document.title
});

Device and Session Information

Amplitude automatically captures device data, but you can enrich it:

amplitude.init('YOUR_API_KEY', null, {
  includeReferrer: true,
  includeUtm: true,
  deviceId: getStoredDeviceId(), // Restore from storage if needed
  sessionTimeout: 30 * 60 * 1000 // 30 minutes
});

// Add custom device/session context
const identify = new amplitude.Identify()
  .set('browser_language', navigator.language)
  .set('screen_resolution', `${screen.width}x${screen.height}`)
  .set('viewport_size', `${window.innerWidth}x${window.innerHeight}`);

amplitude.identify(identify);

User Identity Data

Set user ID upon authentication:

// On login
function handleUserLogin(userData) {
  amplitude.setUserId(userData.user_id);

  const identify = new amplitude.Identify()
    .set('email', userData.email)
    .set('name', userData.name)
    .set('plan', userData.subscription_plan)
    .set('signup_date', userData.created_at)
    .set('account_id', userData.account_id);

  amplitude.identify(identify);

  amplitude.track('user_logged_in', {
    login_method: 'email_password'
  });
}

Ecommerce Data Schema

Define a consistent structure for commerce events:

Product Object

const productData = {
  product_id: 'SKU-12345',
  product_name: 'Wireless Headphones',
  sku: 'SKU-12345',
  category: 'Electronics',
  subcategory: 'Audio',
  brand: 'AudioTech',
  variant: 'Black',
  price: 79.99,
  currency: 'USD',
  quantity: 1,
  position: 0, // In list
  list_name: 'Search Results'
};

amplitude.track('product_viewed', productData);

Cart Operations

// Add to cart
amplitude.track('product_added_to_cart', {
  product_id: 'SKU-12345',
  product_name: 'Wireless Headphones',
  price: 79.99,
  quantity: 1,
  currency: 'USD',
  cart_total: 79.99,
  cart_item_count: 1
});

// Cart viewed
amplitude.track('cart_viewed', {
  cart_total: 142.97,
  cart_item_count: 3,
  currency: 'USD',
  cart_items: ['SKU-12345', 'SKU-67890', 'SKU-11111']
});

Checkout Flow

// Begin checkout
amplitude.track('checkout_started', {
  cart_total: 142.97,
  cart_item_count: 3,
  currency: 'USD',
  coupon_code: 'SUMMER10',
  discount_amount: 14.30
});

// Add shipping info
amplitude.track('shipping_info_added', {
  shipping_method: 'ground',
  shipping_cost: 5.99,
  estimated_delivery: '2024-02-15'
});

// Add payment info
amplitude.track('payment_info_added', {
  payment_method: 'credit_card',
  payment_type: 'Visa'
});

Purchase Confirmation

// Revenue tracking
const revenue = new amplitude.Revenue()
  .setProductId('SKU-12345')
  .setPrice(79.99)
  .setQuantity(1)
  .setRevenueType('purchase')
  .setEventProperties({
    order_id: 'ORD-98765',
    category: 'Electronics'
  });

amplitude.logRevenueV2(revenue);

// Purchase event
amplitude.track('purchase_completed', {
  order_id: 'ORD-98765',
  total_amount: 156.96,
  subtotal: 142.97,
  tax: 8.00,
  shipping: 5.99,
  discount: 14.30,
  currency: 'USD',
  coupon_code: 'SUMMER10',
  item_count: 3,
  payment_method: 'credit_card',
  items: [
    {
      product_id: 'SKU-12345',
      product_name: 'Wireless Headphones',
      price: 79.99,
      quantity: 1
    },
    {
      product_id: 'SKU-67890',
      product_name: 'USB-C Cable',
      price: 12.99,
      quantity: 2
    }
  ]
});

Data Layer Abstraction Pattern

Centralized Data Management

Create a data layer abstraction to normalize data before sending to Amplitude:

// dataLayer.js
class AnalyticsDataLayer {
  constructor() {
    this.pageData = {};
    this.userData = {};
    this.sessionData = {};
  }

  setPageData(data) {
    this.pageData = { ...this.pageData, ...data };
  }

  setUserData(data) {
    this.userData = { ...this.userData, ...data };

    // Sync to Amplitude
    const identify = new amplitude.Identify();
    Object.entries(data).forEach(([key, value]) => {
      identify.set(key, value);
    });
    amplitude.identify(identify);
  }

  trackEvent(eventName, properties = {}) {
    // Merge all context
    const enrichedProperties = {
      ...this.sessionData,
      ...this.pageData,
      ...properties,
      timestamp: new Date().toISOString()
    };

    amplitude.track(eventName, enrichedProperties);
  }

  trackPageView() {
    this.trackEvent('page_viewed', {
      page_path: window.location.pathname,
      page_title: document.title,
      page_url: window.location.href,
      referrer: document.referrer
    });
  }
}

// Initialize
window.dataLayer = new AnalyticsDataLayer();

Usage Example

// Set page context
dataLayer.setPageData({
  page_type: 'product',
  category: 'electronics',
  language: 'en'
});

// Set user context
dataLayer.setUserData({
  plan: 'premium',
  account_id: 'ACC-123'
});

// Track events with automatic enrichment
dataLayer.trackEvent('button_clicked', {
  button_name: 'add_to_cart',
  product_id: 'SKU-12345'
});

Property Naming Conventions

Naming Standards

  • Use snake_case for all property names
  • Be descriptive but concise: product_id not prod_id
  • Use consistent terminology: user_id everywhere, not userId or uid
  • Prefix related properties: utm_source, utm_medium, utm_campaign

Property Type Consistency

Maintain consistent data types for properties:

Property Type Example Notes
product_id string "SKU-12345" Always string, never number
price number 79.99 Always number, never string
quantity number 1 Integer
currency string "USD" ISO 4217 format
timestamp string "2024-01-15T10:30:00Z" ISO 8601 format
is_premium boolean true Never strings like "true"

Reserved Property Names

Avoid using Amplitude's reserved properties:

  • user_id
  • device_id
  • event_id
  • session_id
  • time
  • platform
  • os_name
  • os_version
  • device_brand
  • device_manufacturer
  • device_model

User Properties Management

Setting Properties

// Set property
const identify = new amplitude.Identify().set('plan', 'premium');
amplitude.identify(identify);

// Set once (only if not already set)
const identifyOnce = new amplitude.Identify().setOnce('first_visit_date', '2024-01-15');
amplitude.identify(identifyOnce);

// Append to array
const identifyAppend = new amplitude.Identify().append('purchased_products', 'SKU-12345');
amplitude.identify(identifyAppend);

// Increment numeric value
const identifyIncrement = new amplitude.Identify().add('purchase_count', 1);
amplitude.identify(identifyIncrement);

User Property Schema

Define core user properties:

Property Type Description Example
user_id string Unique user identifier "user_12345"
email string User email (hashed if needed) "user@example.com"
plan string Subscription plan "premium", "free"
signup_date string Account creation date "2024-01-15"
account_type string Account classification "individual", "business"
region string Geographic region "US-West"
language string Preferred language "en"
total_purchases number Lifetime purchase count 5
ltv number Lifetime value 499.99

Group Properties (B2B)

For B2B products, track account-level properties:

// Set group
amplitude.setGroup('company', 'CompanyID-123');

// Set group properties
const groupIdentify = new amplitude.Identify()
  .set('company_name', 'Acme Corp')
  .set('company_plan', 'enterprise')
  .set('seat_count', 50)
  .set('industry', 'technology')
  .set('company_created_date', '2020-05-01');

amplitude.groupIdentify('company', 'CompanyID-123', groupIdentify);

// Events automatically include group context
amplitude.track('feature_used', {
  feature_name: 'advanced_analytics'
});

Amplitude Govern Integration

Schema Validation

Use Amplitude Govern to enforce data quality:

  1. Define expected events and properties
  2. Mark events as "Expected" or "Planned"
  3. Set verification status (Verified, Requires Review, Blocked)
  4. Enable schema validation to reject malformed events

Tracking Plan Definition

// Example tracking plan configuration
const trackingPlan = {
  events: {
    product_viewed: {
      description: 'User viewed a product detail page',
      properties: {
        product_id: { type: 'string', required: true },
        product_name: { type: 'string', required: true },
        price: { type: 'number', required: true },
        category: { type: 'string', required: false }
      }
    },
    purchase_completed: {
      description: 'User completed a purchase',
      properties: {
        order_id: { type: 'string', required: true },
        total_amount: { type: 'number', required: true },
        currency: { type: 'string', required: true },
        item_count: { type: 'number', required: true }
      }
    }
  }
};

Property Blocking

Configure Govern to drop disallowed properties:

  • Blocked: Properties that should never be tracked (PII, sensitive data)
  • Unexpected: Properties not in the tracking plan (flag for review)

Schema Versioning

Version your tracking plan for change management:

amplitude.track('purchase_completed', {
  schema_version: '2.1.0',
  order_id: 'ORD-98765',
  total_amount: 156.99
});

Communicate schema changes:

  1. Minor version bump (2.0.0 → 2.1.0): New optional properties
  2. Major version bump (2.1.0 → 3.0.0): Breaking changes (removed or renamed properties)

Server-Side Data Enrichment

Enrich events with server-side data before sending to Amplitude:

// Server-side (Node.js)
const Amplitude = require('@amplitude/node');

const client = Amplitude.init('YOUR_API_KEY');

app.post('/api/track-purchase', async (req, res) => {
  const { userId, orderId } = req.body;

  // Fetch additional data from database
  const order = await db.orders.findById(orderId);
  const user = await db.users.findById(userId);

  // Send enriched event
  client.logEvent({
    event_type: 'purchase_completed',
    user_id: userId,
    event_properties: {
      order_id: orderId,
      total_amount: order.total,
      item_count: order.items.length,
      // Server-side enrichment
      user_ltv: user.lifetimeValue,
      user_segment: user.segment,
      is_repeat_customer: user.purchaseCount > 1,
      days_since_signup: dateDiff(user.createdAt, new Date())
    }
  });

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

Session Data Management

Track session-level context:

class SessionManager {
  constructor() {
    this.sessionData = this.initializeSession();
  }

  initializeSession() {
    return {
      session_start_time: new Date().toISOString(),
      initial_referrer: document.referrer,
      initial_utm_source: new URLSearchParams(window.location.search).get('utm_source'),
      page_view_count: 0,
      event_count: 0
    };
  }

  incrementPageView() {
    this.sessionData.page_view_count++;
  }

  incrementEvent() {
    this.sessionData.event_count++;
  }

  getSessionData() {
    return {
      ...this.sessionData,
      session_duration_seconds: Math.floor(
        (new Date() - new Date(this.sessionData.session_start_time)) / 1000
      )
    };
  }
}

const sessionManager = new SessionManager();

// Include session data in events
amplitude.track('purchase_completed', {
  ...sessionManager.getSessionData(),
  order_id: 'ORD-98765'
});

Validation and Testing

Event Stream Monitoring

  1. Navigate to Data > Events in Amplitude dashboard
  2. Use Event Stream to view real-time events
  3. Inspect payloads for:
    • Correct property names and casing
    • Expected data types
    • Required properties present
    • No unexpected properties

Property Validation Checklist

Check Expected Result
Property names use snake_case No camelCase or PascalCase
Data types consistent price always number, never string
Required properties present All required fields included
Currency codes valid ISO 4217 format (USD, EUR, GBP)
Timestamps in ISO 8601 "2024-01-15T10:30:00Z"
No PII in properties No email, phone, address unless hashed

Volume and Quality Monitoring

// Track data quality metrics
amplitude.track('data_quality_check', {
  check_type: 'schema_validation',
  events_validated: 1000,
  events_passed: 995,
  events_failed: 5,
  failure_rate: 0.5
});

Monitor in Amplitude:

  1. Event Volume: Track event counts per day/week
  2. Property Coverage: Ensure key properties appear on expected events
  3. Error Rates: Monitor validation failures via Govern

Debug Mode

Enable debug mode to see events in the console:

amplitude.init('YOUR_API_KEY', null, {
  logLevel: 'DEBUG'
});

// Logs will appear in console for every event
amplitude.track('test_event', { test_property: 'test_value' });

Troubleshooting

Symptom Likely Cause Solution
Properties not appearing in Amplitude Property name typo or casing issue Verify exact property name in Event Stream
Inconsistent data types Sending number as string or vice versa Enforce type checking in data layer abstraction
User properties not persisting Using event properties instead Use amplitude.identify() with Identify object
Revenue not tracking Missing Revenue API call Use amplitude.logRevenueV2() for purchase events
Events missing context Not including page/session data Create data layer abstraction to auto-enrich
High unexpected property rate No tracking plan enforcement Enable Govern schema validation
Attribution data lost Not capturing UTM on first page load Store UTM params immediately on landing

Best Practices

  1. Define tracking plan early: Document all events and properties before implementation
  2. Use data layer abstraction: Centralize data management for consistency
  3. Enforce naming conventions: Use linters or validation to ensure snake_case
  4. Version your schema: Track changes and communicate to downstream consumers
  5. Enrich server-side: Add authoritative data from backend systems
  6. Test thoroughly: Validate in Event Stream before production release
  7. Monitor data quality: Set up alerts for schema violations or volume drops
  8. Minimize PII exposure: Hash or exclude sensitive user data
  9. Document everything: Maintain a central tracking plan document
  10. Review regularly: Audit property usage quarterly and deprecate unused properties