AdRoll Data Layer Setup | Blue Frog Docs

AdRoll Data Layer Setup

Configure product catalogs, structured data feeds, and dynamic data layer for AdRoll dynamic retargeting and personalized ad campaigns.

Data Layer Overview

AdRoll's data layer captures product catalog information (SKUs, prices, images, inventory) and user interaction data (products viewed, cart contents, search queries) to power dynamic product ads and personalized retargeting. The data layer feeds product data to AdRoll via JavaScript events on product pages, XML/CSV catalog feeds, and e-commerce platform APIs.

Why Data Layer Matters

  • Dynamic ads: Show users the exact products they viewed or similar items
  • Personalization: Display relevant products based on browsing behavior
  • Inventory sync: Hide out-of-stock items from ads automatically
  • Price accuracy: Ensure ad pricing matches current website prices
  • Campaign optimization: Target users by product category, price range, or brand

Data Layer Components

  1. Product catalog: Complete inventory with IDs, names, prices, images, URLs
  2. Product pageview events: Capture user views of specific products
  3. Category/collection data: Group products for segmentation
  4. Cart/checkout data: Track abandoned cart items for retargeting
  5. Search data: Capture search queries for intent-based targeting

Product Catalog Setup

Catalog Sync Methods

1. E-commerce platform integrations (automatic):

2. XML/CSV feed (manual or scheduled):

  • Host product feed at yoursite.com/products.xml or .csv
  • AdRoll fetches automatically (daily or on-demand)
  • Full control over product attributes and formatting

3. API sync (programmatic):

  • Use AdRoll Product Catalog API for real-time updates
  • Best for custom e-commerce platforms or frequent changes
  • Requires developer implementation

4. Manual upload:

  • Upload CSV in AdRoll dashboard
  • Suitable for small catalogs (under 1000 products)
  • Requires manual updates when products change

Product Catalog Requirements

Required fields:

  • product_id (string): Unique SKU or product identifier
  • product_name (string): Display name for ads
  • price (number): Current price (numeric value only)
  • image_url (string): Full URL to product image (min 600x600px recommended)
  • url (string): Direct link to product detail page

Recommended fields:

  • category (string): Product category for segmentation
  • description (string): Product description for ad copy
  • brand (string): Brand name
  • availability (string): in_stock, out_of_stock, preorder
  • sale_price (number): Discounted price (if on sale)
  • currency (string): ISO currency code (USD, EUR, etc.)

Optional fields:

  • condition (string): new, used, refurbished
  • gender (string): male, female, unisex
  • age_group (string): adult, kids, infant
  • size (string): Product size
  • color (string): Product color
  • custom_label_0 through custom_label_4: Custom attributes

XML Product Feed

Basic XML Feed Structure

<?xml version="1.0" encoding="UTF-8"?>
<products>
  <product>
    <product_id>SKU-001</product_id>
    <product_name><![CDATA[Premium Widget Pro]]></product_name>
    <description><![CDATA[High-quality widget with premium features]]></description>
    <price>49.99</price>
    <currency>USD</currency>
    <image_url>https://example.com/images/widget-pro.jpg</image_url>
    <url>https://example.com/products/premium-widget-pro</url>
    <category>Widgets</category>
    <brand>WidgetCo</brand>
    <availability>in_stock</availability>
  </product>

  <product>
    <product_id>SKU-002</product_id>
    <product_name><![CDATA[Standard Widget]]></product_name>
    <description><![CDATA[Reliable everyday widget]]></description>
    <price>29.99</price>
    <sale_price>24.99</sale_price>
    <currency>USD</currency>
    <image_url>https://example.com/images/widget-standard.jpg</image_url>
    <url>https://example.com/products/standard-widget</url>
    <category>Widgets</category>
    <brand>WidgetCo</brand>
    <availability>in_stock</availability>
  </product>
</products>

Key formatting rules:

  • Use <![CDATA[...]]> for text fields with special characters
  • Price values must be numeric (no currency symbols)
  • URLs must be absolute (include https://)
  • Image URLs must be accessible (not password-protected)

Generate XML Feed (E-Commerce Platforms)

Shopify (using app or Liquid template):

{%- comment -%}
  Create new template: Templates → product.feed.xml
{%- endcomment -%}

<?xml version="1.0" encoding="UTF-8"?>
<products>
  {% for product in collections.all.products %}
  <product>
    <product_id>{{ product.id }}</product_id>
    <product_name><![CDATA[{{ product.title | escape }}]]></product_name>
    <description><![CDATA[{{ product.description | strip_html | escape }}]]></description>
    <price>{{ product.price | money_without_currency | remove: ',' }}</price>
    <currency>{{ shop.currency }}</currency>
    <image_url>{{ product.featured_image | img_url: 'grande' | prepend: 'https:' }}</image_url>
    <url>{{ shop.url }}{{ product.url }}</url>
    <category>{{ product.type }}</category>
    <brand>{{ product.vendor }}</brand>
    <availability>{% if product.available %}in_stock{% else %}out_of_stock{% endif %}</availability>
  </product>
  {% endfor %}
</products>

{%- comment -%}
  Access at: yourstore.myshopify.com/products.xml
{%- endcomment -%}

WooCommerce (using plugin or custom code):

// functions.php or custom plugin
function generate_adroll_product_feed() {
  header('Content-Type: application/xml; charset=UTF-8');

  $args = array(
    'post_type' => 'product',
    'posts_per_page' => -1,
    'post_status' => 'publish'
  );

  $products = new WP_Query($args);

  echo '<?xml version="1.0" encoding="UTF-8"?>';
  echo '<products>';

  while ($products->have_posts()) {
    $products->the_post();
    $product = wc_get_product(get_the_ID());

    echo '<product>';
    echo '<product_id>' . esc_xml($product->get_sku() ?: $product->get_id()) . '</product_id>';
    echo '<product_name><![CDATA[' . esc_xml($product->get_name()) . ']]></product_name>';
    echo '<description><![CDATA[' . esc_xml(wp_strip_all_tags($product->get_description())) . ']]></description>';
    echo '<price>' . $product->get_price() . '</price>';
    echo '<currency>' . get_woocommerce_currency() . '</currency>';
    echo '<image_url>' . esc_url(wp_get_attachment_url($product->get_image_id())) . '</image_url>';
    echo '<url>' . esc_url(get_permalink()) . '</url>';

    $categories = wc_get_product_category_list($product->get_id(), ', ');
    echo '<category><![CDATA[' . wp_strip_all_tags($categories) . ']]></category>';

    echo '<brand><![CDATA[' . esc_xml($product->get_attribute('brand')) . ']]></brand>';
    echo '<availability>' . ($product->is_in_stock() ? 'in_stock' : 'out_of_stock') . '</availability>';
    echo '</product>';
  }

  echo '</products>';

  wp_reset_postdata();
  exit;
}

// Create custom endpoint: yoursite.com/product-feed.xml
add_action('init', function() {
  add_rewrite_rule('^product-feed\.xml$', 'index.php?adroll_feed=1', 'top');
});

add_filter('query_vars', function($vars) {
  $vars[] = 'adroll_feed';
  return $vars;
});

add_action('template_redirect', function() {
  if (get_query_var('adroll_feed')) {
    generate_adroll_product_feed();
  }
});

Custom PHP (generic):

<?php
// product-feed.php
header('Content-Type: application/xml; charset=UTF-8');

// Fetch products from database
$db = new PDO('mysql:host=localhost;dbname=yourdb', 'user', 'password');
$stmt = $db->query('SELECT * FROM products WHERE status = "active"');
$products = $stmt->fetchAll(PDO::FETCH_ASSOC);

echo '<?xml version="1.0" encoding="UTF-8"?>';
echo '<products>';

foreach ($products as $product) {
  echo '<product>';
  echo '<product_id>' . htmlspecialchars($product['sku']) . '</product_id>';
  echo '<product_name><![CDATA[' . htmlspecialchars($product['name']) . ']]></product_name>';
  echo '<description><![CDATA[' . htmlspecialchars($product['description']) . ']]></description>';
  echo '<price>' . number_format($product['price'], 2, '.', '') . '</price>';
  echo '<currency>USD</currency>';
  echo '<image_url>' . htmlspecialchars($product['image_url']) . '</image_url>';
  echo '<url>' . htmlspecialchars($product['url']) . '</url>';
  echo '<category>' . htmlspecialchars($product['category']) . '</category>';
  echo '<brand>' . htmlspecialchars($product['brand']) . '</brand>';
  echo '<availability>' . ($product['stock'] > 0 ? 'in_stock' : 'out_of_stock') . '</availability>';
  echo '</product>';
}

echo '</products>';
?>

Upload Feed to AdRoll

Method 1: Hosted feed (recommended)

  1. Upload XML file to web server: https://example.com/adroll-products.xml
  2. AdRoll → Products → Catalog → Add Feed
  3. Enter feed URL: https://example.com/adroll-products.xml
  4. Set sync frequency: Daily, Weekly, or Manual
  5. Click Validate & Save
  6. AdRoll fetches and processes feed

Method 2: Manual upload

  1. Export products to XML or CSV
  2. AdRoll → Products → Catalog → Upload File
  3. Select file from computer
  4. Click Upload
  5. Manually re-upload when products change

CSV Product Feed

CSV Format

product_id,product_name,description,price,currency,image_url,url,category,brand,availability
SKU-001,"Premium Widget Pro","High-quality widget",49.99,USD,https://example.com/images/widget-pro.jpg,https://example.com/products/premium-widget-pro,Widgets,WidgetCo,in_stock
SKU-002,"Standard Widget","Reliable widget",29.99,USD,https://example.com/images/widget-std.jpg,https://example.com/products/standard-widget,Widgets,WidgetCo,in_stock
SKU-003,"Widget Accessories","Compatible accessories",9.99,USD,https://example.com/images/accessories.jpg,https://example.com/products/accessories,Accessories,WidgetCo,in_stock

CSV requirements:

  • First row must be header with field names
  • Use double quotes for fields with commas or special characters
  • UTF-8 encoding for international characters
  • No empty rows between products

Generate CSV Feed

Python example:

import csv
import mysql.connector

# Connect to database
db = mysql.connector.connect(
  host="localhost",
  user="user",
  password="password",
  database="yourdb"
)

cursor = db.cursor(dictionary=True)
cursor.execute("SELECT * FROM products WHERE status = 'active'")
products = cursor.fetchall()

# Write CSV
with open('adroll-products.csv', 'w', newline='', encoding='utf-8') as csvfile:
  fieldnames = ['product_id', 'product_name', 'description', 'price', 'currency',
                'image_url', 'url', 'category', 'brand', 'availability']
  writer = csv.DictWriter(csvfile, fieldnames=fieldnames)

  writer.writeheader()
  for product in products:
    writer.writerow({
      'product_id': product['sku'],
      'product_name': product['name'],
      'description': product['description'],
      'price': product['price'],
      'currency': 'USD',
      'image_url': product['image_url'],
      'url': product['url'],
      'category': product['category'],
      'brand': product['brand'],
      'availability': 'in_stock' if product['stock'] > 0 else 'out_of_stock'
    })

print("Feed generated: adroll-products.csv")

Product Pageview Tracking

Basic Product Data Layer

Send product details when user views product page:

// Fire on product detail pages
adroll.track("pageView", {
  product_id: "SKU-001",
  product_name: "Premium Widget Pro",
  category: "Widgets",
  price: 49.99,
  image_url: "https://example.com/images/widget-pro.jpg",
  url: "https://example.com/products/premium-widget-pro",
  inventory_status: "in_stock"
});

E-Commerce Platform Implementations

Shopify (product.liquid):

<!-- templates/product.liquid -->
<script>
  adroll.track("pageView", {
    product_id: "{{ product.id }}",
    product_name: "{{ product.title | escape }}",
    category: "{{ product.type }}",
    price: {{ product.price | money_without_currency | remove: ',' }},
    {% if product.compare_at_price > product.price %}
    sale_price: {{ product.price | money_without_currency | remove: ',' }},
    regular_price: {{ product.compare_at_price | money_without_currency | remove: ',' }},
    {% endif %}
    image_url: "https:{{ product.featured_image | img_url: 'grande' }}",
    url: "{{ shop.url }}{{ product.url }}",
    brand: "{{ product.vendor }}",
    inventory_status: "{% if product.available %}in_stock{% else %}out_of_stock{% endif %}",
    currency: "{{ shop.currency }}"
  });
</script>

WooCommerce (functions.php):

function adroll_product_pageview() {
  if (!is_product()) {
    return;
  }

  global $product;
  ?>
  <script>
    adroll.track("pageView", {
      product_id: "<?php echo esc_js($product->get_sku() ?: $product->get_id()); ?>",
      product_name: "<?php echo esc_js($product->get_name()); ?>",
      category: "<?php echo esc_js(wp_strip_all_tags(wc_get_product_category_list($product->get_id()))); ?>",
      price: <?php echo $product->get_price(); ?>,
      <?php if ($product->is_on_sale()): ?>
      sale_price: <?php echo $product->get_sale_price(); ?>,
      regular_price: <?php echo $product->get_regular_price(); ?>,
      <?php endif; ?>
      image_url: "<?php echo esc_url(wp_get_attachment_url($product->get_image_id())); ?>",
      url: "<?php echo esc_url(get_permalink()); ?>",
      brand: "<?php echo esc_js($product->get_attribute('brand')); ?>",
      inventory_status: "<?php echo $product->is_in_stock() ? 'in_stock' : 'out_of_stock'; ?>",
      currency: "<?php echo get_woocommerce_currency(); ?>"
    });
  </script>
  <?php
}
add_action('wp_footer', 'adroll_product_pageview');

BigCommerce (Handlebars):

{{!-- templates/pages/product.html --}}
{{#if product}}
<script>
  adroll.track("pageView", {
    product_id: "{{product.sku}}",
    product_name: "{{product.title}}",
    category: "{{product.category}}",
    price: {{product.price.without_tax.value}},
    image_url: "{{getImage product.main_image 'product_size'}}",
    url: "{{product.url}}",
    brand: "{{product.brand.name}}",
    inventory_status: "{{#if product.out_of_stock}}out_of_stock{{else}}in_stock{{/if}}",
    currency: "{{currency_selector.active_currency_code}}"
  });
</script>
{{/if}}

Product Variants Tracking

For products with variants (size, color):

// Track parent product + selected variant
adroll.track("pageView", {
  product_id: "SKU-001-BLU-L", // Variant-specific ID
  parent_id: "SKU-001",        // Parent product ID
  product_name: "Premium Widget Pro - Blue, Large",
  variant: {
    color: "Blue",
    size: "Large"
  },
  price: 49.99,
  // ... other fields
});

// Or track variant selection separately
document.querySelectorAll('.variant-selector').forEach(select => {
  select.addEventListener('change', function() {
    const selectedVariant = getSelectedVariant(); // Your function
    adroll.track("pageView", {
      product_id: selectedVariant.sku,
      price: selectedVariant.price,
      variant: selectedVariant.options
    });
  });
});

Category & Collection Tracking

Category Page Data Layer

// Fire on category/collection pages
adroll.track("pageView", {
  segment_name: "category_widgets",
  category: "Widgets",
  page_type: "category",
  product_count: 24 // Number of products in category
});

Shopify Collection Tracking

<!-- templates/collection.liquid -->
<script>
  adroll.track("pageView", {
    segment_name: "collection_{{ collection.handle }}",
    category: "{{ collection.title | escape }}",
    page_type: "collection",
    product_count: {{ collection.products_count }}
  });
</script>

Multiple Products on Page

Track all visible products on category pages:

// Track multiple products (e.g., on category page)
const products = [];
document.querySelectorAll('.product-item').forEach(item => {
  products.push({
    product_id: item.dataset.productId,
    product_name: item.dataset.productName,
    price: parseFloat(item.dataset.price)
  });
});

adroll.track("pageView", {
  category: "Widgets",
  products_viewed: products
});

Cart & Checkout Data Layer

Cart Contents Tracking

// Track cart contents for abandoned cart campaigns
adroll.track("pageView", {
  page_type: "cart",
  cart_total: 149.97,
  currency: "USD",
  cart_items: [
    {product_id: "SKU-001", quantity: 2, price: 49.99},
    {product_id: "SKU-002", quantity: 1, price: 49.99}
  ]
});

Checkout Step Tracking

// Track checkout funnel progress
adroll.track("pageView", {
  page_type: "checkout",
  checkout_step: "shipping", // or "payment", "review"
  cart_total: 149.97,
  currency: "USD"
});

Search Query Tracking

Search Results Data Layer

// Track search queries for intent-based targeting
adroll.track("pageView", {
  segment_name: "search_users",
  search_query: "blue widgets",
  search_results_count: 12,
  page_type: "search"
});

No-Results Tracking

// Track searches with no results (potential new product opportunities)
adroll.track("pageView", {
  segment_name: "zero_search_results",
  search_query: "purple widgets",
  search_results_count: 0
});

Data Layer Validation

Check Product Data in Browser

// Open browser console on product page
// Verify product data is being sent:

// 1. Check if AdRoll is loaded
console.log(window.adroll);

// 2. Manually trigger product pageview
adroll.track("pageView", {
  product_id: "TEST-SKU",
  product_name: "Test Product",
  price: 10.00
});

// 3. Check Network tab
// Filter: "adroll"
// Look for request with product parameters

Validate Catalog Feed

Before uploading to AdRoll:

  1. XML validation:

  2. CSV validation:

    • Open in spreadsheet (Excel, Google Sheets)
    • Check for missing required fields
    • Verify no empty rows or extra columns
  3. URL accessibility:

    • Visit all image URLs in browser (should load images)
    • Visit product URLs (should load product pages)
    • Check for broken links or 404 errors

In AdRoll dashboard:

  1. AdRoll → Products → Catalog → Diagnostics
  2. Review errors/warnings:
    • Missing required fields
    • Invalid URLs
    • Price format errors
    • Image size issues
  3. Fix issues and re-upload/resync feed

Advanced Data Layer Techniques

Dynamic Pricing

// Update price dynamically based on user actions
function updateAdRollPrice(newPrice) {
  adroll.track("pageView", {
    product_id: currentProduct.id,
    price: newPrice,
    price_updated: true
  });
}

// Example: Quantity discount
document.getElementById('quantity').addEventListener('change', function() {
  const qty = parseInt(this.value);
  const basePrice = 49.99;
  const discountedPrice = qty >= 10 ? basePrice * 0.9 : basePrice;

  updateAdRollPrice(discountedPrice);
});

Multi-Currency Support

// Detect user's selected currency
const userCurrency = getCurrencyFromSelector(); // Your function

adroll.track("pageView", {
  product_id: "SKU-001",
  price: convertPrice(49.99, userCurrency), // Convert to user currency
  currency: userCurrency // "EUR", "GBP", etc.
});

Geolocation-Based Inventory

// Show region-specific availability
fetch('/api/check-inventory?sku=SKU-001&region=' + userRegion)
  .then(res => res.json())
  .then(data => {
    adroll.track("pageView", {
      product_id: "SKU-001",
      inventory_status: data.in_stock ? "in_stock" : "out_of_stock",
      region: userRegion
    });
  });

Troubleshooting Data Layer Issues

Product ads not showing correct products:

  • Verify product_id matches exactly between feed and pageview events
  • Check catalog sync status in AdRoll dashboard
  • Allow 48 hours after catalog update for ads to refresh

Prices outdated in ads:

  • Ensure catalog feed syncs daily (or more frequently)
  • Check product pageview events send current prices
  • Verify price format (numeric, no symbols)

Images not showing in ads:

  • Image URLs must be absolute (https://, not relative /images/)
  • Images must be accessible (not behind login or password)
  • Recommended size: 600x600px minimum, 1200x1200px ideal
  • Supported formats: JPG, PNG, WebP

Products disappearing from catalog:

  • Check availability field (AdRoll hides out_of_stock by default)
  • Verify feed URL still accessible
  • Review catalog diagnostics for errors
  • Ensure product still exists in feed (not deleted)

Next Steps

// SYS.FOOTER