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
- Product catalog: Complete inventory with IDs, names, prices, images, URLs
- Product pageview events: Capture user views of specific products
- Category/collection data: Group products for segmentation
- Cart/checkout data: Track abandoned cart items for retargeting
- Search data: Capture search queries for intent-based targeting
Product Catalog Setup
Catalog Sync Methods
1. E-commerce platform integrations (automatic):
- Shopify, WooCommerce, BigCommerce, Magento apps auto-sync catalogs
- Updates every 6-24 hours depending on platform
- Zero configuration required after app installation
2. XML/CSV feed (manual or scheduled):
- Host product feed at
yoursite.com/products.xmlor.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 identifierproduct_name(string): Display name for adsprice(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 segmentationdescription(string): Product description for ad copybrand(string): Brand nameavailability(string):in_stock,out_of_stock,preordersale_price(number): Discounted price (if on sale)currency(string): ISO currency code (USD, EUR, etc.)
Optional fields:
condition(string):new,used,refurbishedgender(string):male,female,unisexage_group(string):adult,kids,infantsize(string): Product sizecolor(string): Product colorcustom_label_0throughcustom_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)
- Upload XML file to web server:
https://example.com/adroll-products.xml - AdRoll → Products → Catalog → Add Feed
- Enter feed URL:
https://example.com/adroll-products.xml - Set sync frequency: Daily, Weekly, or Manual
- Click Validate & Save
- AdRoll fetches and processes feed
Method 2: Manual upload
- Export products to XML or CSV
- AdRoll → Products → Catalog → Upload File
- Select file from computer
- Click Upload
- 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:
XML validation:
- Use online validator: https://www.xmlvalidation.com
- Check for syntax errors, unclosed tags
- Verify special characters are in CDATA
CSV validation:
- Open in spreadsheet (Excel, Google Sheets)
- Check for missing required fields
- Verify no empty rows or extra columns
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:
- AdRoll → Products → Catalog → Diagnostics
- Review errors/warnings:
- Missing required fields
- Invalid URLs
- Price format errors
- Image size issues
- 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®ion=' + 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_idmatches 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
availabilityfield (AdRoll hidesout_of_stockby default) - Verify feed URL still accessible
- Review catalog diagnostics for errors
- Ensure product still exists in feed (not deleted)
Next Steps
- Event Tracking - Configure conversion events
- Cross-Domain Tracking - Multi-site product tracking
- Troubleshooting & Debugging - Fix catalog sync issues
- Integrations - E-commerce platform integrations