TikTok Ads Data Layer Setup | OpsBlu Docs

TikTok Ads Data Layer Setup

Complete data layer implementation for Google Tag Manager on TikTok Ads. Covers page metadata, user properties, ecommerce data, and custom event variables.

Data Layer Overview

A data layer is a JavaScript object that stores structured data about page content, user interactions, and ecommerce transactions. It serves as a single source of truth for tracking implementations.

Benefits of Data Layer

Benefit Description
Separation of concerns Business logic separate from tracking
Maintainability Update tracking without changing app code
Tag manager friendly Easy GTM integration
Consistency Same data structure across platforms
Testing Easier to validate data

Basic Data Layer Structure

Global Data Layer

Initialize on all pages:

// Initialize before TikTok Pixel loads
window.dataLayer = window.dataLayer || [];
window.dataLayer.push({
  'pageType': 'homepage',
  'userStatus': 'logged_in',
  'userId': 'USER_12345',
  'userEmail': 'user@example.com',  // Will be hashed
  'userPhone': '+15551234567'  // Will be hashed
});

Page-Specific Data

Add page context:

// Product page
dataLayer.push({
  'pageType': 'product',
  'product': {
    'id': 'SKU_123',
    'name': 'Blue Widget',
    'category': 'Widgets',
    'price': 99.99,
    'currency': 'USD',
    'availability': 'in_stock'
  }
});

// Category page
dataLayer.push({
  'pageType': 'category',
  'category': {
    'name': 'Widgets',
    'id': 'CAT_001',
    'productCount': 24
  }
});

// Cart page
dataLayer.push({
  'pageType': 'cart',
  'cart': {
    'itemCount': 3,
    'totalValue': 249.97,
    'currency': 'USD',
    'items': [
      {
        'id': 'SKU_123',
        'name': 'Blue Widget',
        'quantity': 2,
        'price': 99.99
      },
      {
        'id': 'SKU_456',
        'name': 'Red Widget',
        'quantity': 1,
        'price': 49.99
      }
    ]
  }
});

Ecommerce Data Layer

Enhanced Ecommerce Format

Follow Google's Enhanced Ecommerce structure for compatibility:

dataLayer.push({
  'event': 'ecommerce',
  'ecommerce': {
    'currencyCode': 'USD',
    'impressions': [
      {
        'id': 'SKU_123',
        'name': 'Blue Widget',
        'category': 'Widgets',
        'price': '99.99',
        'position': 1
      }
    ]
  }
});

Product Detail View

// When user views product
dataLayer.push({
  'event': 'productDetail',
  'ecommerce': {
    'currencyCode': 'USD',
    'detail': {
      'actionField': {'list': 'Search Results'},
      'products': [{
        'id': 'SKU_123',
        'name': 'Blue Widget',
        'category': 'Widgets',
        'variant': 'Blue',
        'price': '99.99',
        'brand': 'WidgetCo'
      }]
    }
  }
});

Add to Cart

// When user adds to cart
dataLayer.push({
  'event': 'addToCart',
  'ecommerce': {
    'currencyCode': 'USD',
    'add': {
      'products': [{
        'id': 'SKU_123',
        'name': 'Blue Widget',
        'category': 'Widgets',
        'price': '99.99',
        'quantity': 2
      }]
    }
  }
});

Checkout Steps

// Checkout initiated
dataLayer.push({
  'event': 'checkout',
  'ecommerce': {
    'checkout': {
      'actionField': {'step': 1, 'option': 'Visa'},
      'products': [{
        'id': 'SKU_123',
        'name': 'Blue Widget',
        'price': '99.99',
        'quantity': 2
      }]
    }
  }
});

// Checkout step 2 (shipping)
dataLayer.push({
  'event': 'checkoutOption',
  'ecommerce': {
    'checkout_option': {
      'actionField': {'step': 2, 'option': 'Standard Shipping'}
    }
  }
});

Purchase

// On order confirmation page
dataLayer.push({
  'event': 'purchase',
  'ecommerce': {
    'purchase': {
      'actionField': {
        'id': 'ORDER_12345',
        'revenue': '199.98',
        'tax': '16.00',
        'shipping': '10.00',
        'coupon': 'SUMMER10'
      },
      'products': [
        {
          'id': 'SKU_123',
          'name': 'Blue Widget',
          'category': 'Widgets',
          'price': '99.99',
          'quantity': 2
        }
      ]
    }
  }
});

TikTok Pixel Integration with Data Layer

Reading from Data Layer

// Wait for data layer, then fire TikTok event
function getDataLayer(key) {
  for (var i = 0; i < window.dataLayer.length; i++) {
    if (window.dataLayer[i][key] !== undefined) {
      return window.dataLayer[i][key];
    }
  }
  return null;
}

// Fire ViewContent based on data layer
var product = getDataLayer('product');
if (product) {
  ttq.track('ViewContent', {
    content_type: 'product',
    content_id: product.id,
    content_name: product.name,
    content_category: product.category,
    value: product.price,
    currency: product.currency
  });
}

Event Listener Pattern

// Listen for data layer events
window.dataLayer = window.dataLayer || [];

// Store original push method
var originalPush = window.dataLayer.push;

// Override push to intercept events
window.dataLayer.push = function() {
  // Call original push
  originalPush.apply(window.dataLayer, arguments);

  // Check for ecommerce events
  var lastEvent = arguments[0];

  if (lastEvent.event === 'addToCart' && lastEvent.ecommerce) {
    var product = lastEvent.ecommerce.add.products[0];

    ttq.track('AddToCart', {
      content_type: 'product',
      content_id: product.id,
      content_name: product.name,
      quantity: product.quantity,
      price: parseFloat(product.price),
      value: parseFloat(product.price) * product.quantity,
      currency: lastEvent.ecommerce.currencyCode
    });
  }

  if (lastEvent.event === 'purchase' && lastEvent.ecommerce) {
    var purchase = lastEvent.ecommerce.purchase;

    ttq.track('CompletePayment', {
      value: parseFloat(purchase.actionField.revenue),
      currency: lastEvent.ecommerce.currencyCode,
      order_id: purchase.actionField.id,
      contents: purchase.products.map(function(p) {
        return {
          content_id: p.id,
          content_name: p.name,
          quantity: p.quantity,
          price: parseFloat(p.price)
        };
      })
    });
  }
};

Platform-Specific Data Layers

Shopify Data Layer

<!-- Shopify Liquid template -->
<script>
window.dataLayer = window.dataLayer || [];

{% if template.name == 'product' %}
dataLayer.push({
  'pageType': 'product',
  'product': {
    'id': '{{ product.id }}',
    'sku': '{{ product.selected_or_first_available_variant.sku }}',
    'name': '{{ product.title | escape }}',
    'category': '{{ product.type }}',
    'price': {{ product.price | money_without_currency }},
    'currency': '{{ shop.currency }}',
    'vendor': '{{ product.vendor }}',
    'available': {{ product.available }}
  }
});
{% endif %}

{% if template.name == 'cart' %}
dataLayer.push({
  'pageType': 'cart',
  'cart': {
    'totalValue': {{ cart.total_price | money_without_currency }},
    'currency': '{{ shop.currency }}',
    'itemCount': {{ cart.item_count }},
    'items': [
      {% for item in cart.items %}
      {
        'id': '{{ item.product_id }}',
        'sku': '{{ item.sku }}',
        'name': '{{ item.product.title | escape }}',
        'quantity': {{ item.quantity }},
        'price': {{ item.price | money_without_currency }}
      }{% unless forloop.last %},{% endunless %}
      {% endfor %}
    ]
  }
});
{% endif %}
</script>

WooCommerce Data Layer

// functions.php
function add_woocommerce_datalayer() {
  global $product, $wp_query;

  $data = array();

  // Product page
  if (is_product()) {
    $data['pageType'] = 'product';
    $data['product'] = array(
      'id' => $product->get_id(),
      'sku' => $product->get_sku(),
      'name' => $product->get_name(),
      'category' => strip_tags($product->get_categories()),
      'price' => floatval($product->get_price()),
      'currency' => get_woocommerce_currency()
    );
  }

  // Cart page
  if (is_cart() && !WC()->cart->is_empty()) {
    $cart_items = array();

    foreach (WC()->cart->get_cart() as $cart_item) {
      $cart_product = $cart_item['data'];
      $cart_items[] = array(
        'id' => $cart_product->get_id(),
        'sku' => $cart_product->get_sku(),
        'name' => $cart_product->get_name(),
        'quantity' => $cart_item['quantity'],
        'price' => floatval($cart_product->get_price())
      );
    }

    $data['pageType'] = 'cart';
    $data['cart'] = array(
      'totalValue' => floatval(WC()->cart->get_total('edit')),
      'currency' => get_woocommerce_currency(),
      'itemCount' => WC()->cart->get_cart_contents_count(),
      'items' => $cart_items
    );
  }

  // Output data layer
  if (!empty($data)) {
    ?>
    <script>
    window.dataLayer = window.dataLayer || [];
    dataLayer.push(<?php echo json_encode($data); ?>);
    </script>
    <?php
  }
}
add_action('wp_head', 'add_woocommerce_datalayer');

Magento Data Layer

<!-- Magento template -->
<script>
window.dataLayer = window.dataLayer || [];

<?php if ($product = $block->getProduct()): ?>
dataLayer.push({
  'pageType': 'product',
  'product': {
    'id': '<?php echo $product->getId(); ?>',
    'sku': '<?php echo $product->getSku(); ?>',
    'name': '<?php echo $this->escapeHtml($product->getName()); ?>',
    'price': <?php echo $product->getFinalPrice(); ?>,
    'currency': '<?php echo $this->getCurrency(); ?>'
  }
});
<?php endif; ?>
</script>

Single Page Application (SPA) Data Layer

React Example

// DataLayerContext.js
import React, { createContext, useContext, useState } from 'react';

const DataLayerContext = createContext();

export function DataLayerProvider({ children }) {
  const [dataLayer, setDataLayer] = useState([]);

  const pushToDataLayer = (data) => {
    setDataLayer(prev => [...prev, data]);
    window.dataLayer = window.dataLayer || [];
    window.dataLayer.push(data);
  };

  return (
    <DataLayerContext.Provider value={{ dataLayer, pushToDataLayer }}>
      {children}
    </DataLayerContext.Provider>
  );
}

export const useDataLayer = () => useContext(DataLayerContext);
// ProductPage.js
import { useEffect } from 'react';
import { useDataLayer } from './DataLayerContext';

function ProductPage({ product }) {
  const { pushToDataLayer } = useDataLayer();

  useEffect(() => {
    // Push product data to data layer
    pushToDataLayer({
      'event': 'productDetail',
      'pageType': 'product',
      'product': {
        'id': product.sku,
        'name': product.name,
        'price': product.price,
        'currency': 'USD'
      }
    });

    // Fire TikTok event
    if (typeof window.ttq !== 'undefined') {
      window.ttq.track('ViewContent', {
        content_type: 'product',
        content_id: product.sku,
        content_name: product.name,
        value: product.price,
        currency: 'USD'
      });
    }
  }, [product, pushToDataLayer]);

  return <div>{/* Product UI */}</div>;
}

Vue.js Example

// store/datalayer.js
export default {
  state: {
    events: []
  },

  mutations: {
    PUSH_EVENT(state, event) {
      state.events.push(event);
      window.dataLayer = window.dataLayer || [];
      window.dataLayer.push(event);
    }
  },

  actions: {
    pushToDataLayer({ commit }, event) {
      commit('PUSH_EVENT', event);
    }
  }
};
// ProductView.vue
export default {
  mounted() {
    this.$store.dispatch('pushToDataLayer', {
      event: 'productDetail',
      pageType: 'product',
      product: {
        id: this.product.sku,
        name: this.product.name,
        price: this.product.price,
        currency: 'USD'
      }
    });

    ttq.track('ViewContent', {
      content_type: 'product',
      content_id: this.product.sku,
      content_name: this.product.name,
      value: this.product.price,
      currency: 'USD'
    });
  }
};

User Data in Data Layer

Hashing PII

Always hash personally identifiable information:

// Hash function (use crypto-js or similar library)
function hashSHA256(data) {
  return CryptoJS.SHA256(data.toLowerCase().trim()).toString();
}

// Push user data to data layer (hashed)
dataLayer.push({
  'user': {
    'id': 'USER_12345',  // Internal ID (not PII)
    'emailHash': hashSHA256('user@example.com'),
    'phoneHash': hashSHA256('+15551234567'),
    'status': 'logged_in',
    'lifetime_value': 2500.00,
    'first_purchase_date': '2024-01-15'
  }
});

// Use in TikTok advanced matching
ttq.identify({
  email: dataLayer.find(d => d.user)?.user.emailHash,
  phone_number: dataLayer.find(d => d.user)?.user.phoneHash,
  external_id: dataLayer.find(d => d.user)?.user.id
});

GTM Integration

Reading Data Layer in GTM

Create Data Layer Variables:

  1. Variable Type: Data Layer Variable
  2. Variable Name: Product ID
  3. Data Layer Variable Name: product.id

Use in TikTok Event Tag:

<script>
ttq.track('ViewContent', {
  content_type: 'product',
  content_id: '{{Product ID}}',
  content_name: '{{Product Name}}',
  value: {{Product Price}},
  currency: '{{Currency}}'
});
</script>

Event-Driven Tags

Trigger Configuration:

  • Trigger Type: Custom Event
  • Event Name: addToCart
  • This trigger fires on: All Custom Events

Tag Fires When:

// Website code
dataLayer.push({
  'event': 'addToCart',
  'product': {
    'id': 'SKU_123',
    'name': 'Blue Widget',
    'price': 99.99
  }
});

// GTM tag automatically fires

Data Layer Validation

Validation Function

function validateDataLayer(data) {
  const errors = [];

  // Check required fields
  if (!data.pageType) {
    errors.push('Missing pageType');
  }

  // Validate product data
  if (data.pageType === 'product' && data.product) {
    if (!data.product.id) errors.push('Missing product.id');
    if (!data.product.name) errors.push('Missing product.name');
    if (!data.product.price) errors.push('Missing product.price');
    if (!data.product.currency) errors.push('Missing product.currency');
  }

  // Validate purchase data
  if (data.event === 'purchase' && data.ecommerce?.purchase) {
    const purchase = data.ecommerce.purchase;
    if (!purchase.actionField?.id) {
      errors.push('Missing transaction ID');
    }
    if (!purchase.actionField?.revenue) {
      errors.push('Missing transaction revenue');
    }
    if (!purchase.products || purchase.products.length === 0) {
      errors.push('Missing products array');
    }
  }

  if (errors.length > 0) {
    console.error('Data Layer Validation Errors:', errors);
    return false;
  }

  return true;
}

// Use before pushing to data layer
const productData = {
  pageType: 'product',
  product: {
    id: 'SKU_123',
    name: 'Blue Widget',
    price: 99.99,
    currency: 'USD'
  }
};

if (validateDataLayer(productData)) {
  dataLayer.push(productData);
}

Browser Console Inspector

// Inspect current data layer
console.table(window.dataLayer);

// Find specific data
window.dataLayer.filter(d => d.event === 'purchase');

// Get latest product data
window.dataLayer.filter(d => d.product).pop();

Best Practices

Structure

  1. Initialize early - Before tracking scripts load
  2. Use consistent naming - camelCase or snake_case
  3. Shallow objects - Avoid deep nesting
  4. Push events - Don't overwrite data layer
  5. Document structure - Maintain data layer schema

Performance

  1. Batch updates - Push multiple values at once
  2. Avoid redundancy - Don't push duplicate data
  3. Clean old data - Remove unnecessary historical events
  4. Lazy load - Don't populate unused data

Security

  1. Hash PII - Email, phone, address
  2. Sanitize input - Escape special characters
  3. Validate data - Check types and formats
  4. Limit exposure - Only include necessary data

Testing

  1. Console logging - View data layer in browser
  2. GTM Preview mode - Validate data layer variables
  3. Automated tests - Unit test data layer logic
  4. QA checklist - Verify on all page types

Data Layer Schema Documentation

## Data Layer Schema

### Global Variables

| Field | Type | Description | Required |
|-------|------|-------------|----------|
| `pageType` | String | Page category | Yes |
| `user.id` | String | User identifier | No |
| `user.emailHash` | String | SHA256 hashed email | No |
| `user.status` | String | logged_in/guest | Yes |

### Product Object

| Field | Type | Description | Required |
|-------|------|-------------|----------|
| `product.id` | String | Product SKU | Yes |
| `product.name` | String | Product name | Yes |
| `product.price` | Number | Product price | Yes |
| `product.currency` | String | ISO currency code | Yes |
| `product.category` | String | Product category | No |

### Cart Object

| Field | Type | Description | Required |
|-------|------|-------------|----------|
| `cart.totalValue` | Number | Total cart value | Yes |
| `cart.currency` | String | ISO currency code | Yes |
| `cart.itemCount` | Number | Number of items | Yes |
| `cart.items` | Array | Array of products | Yes |

Next Steps

  1. Implement data layer on your website
  2. Integrate with TikTok Pixel using examples above
  3. Set up GTM if using tag manager
  4. Validate data using browser console
  5. Test events with TikTok Pixel Helper