Fixing Broken Ecommerce & Lead Tracking After Shopify-to-WooCommerce Migration for SmileSellers
How I restored full-funnel GA4 ecommerce tracking, checkout milestones, and micro-conversion telemetry for SmileSellers after migrating from Shopify to WordPress WooCommerce.
The Result at a Glance:
Migration from Shopify to WordPress WooCommerce broke all ecommerce tracking and lead capture events.
Shopify liquid tracking scripts were lost; new WooCommerce theme lacked server-side Data Layer pushes and AJAX cart listeners.
Built a unified WooCommerce Data Layer contract + GTM tag architecture covering the 10-step ecommerce funnel and 5 key micro-conversions.
Restored 100% ecommerce attribution, 0% purchase drop-off, and reliable ROAS optimization for paid media campaigns.
Digital Measurement Analyst / Analytics Engineer
Measurement audit → WooCommerce DataLayer Architecture → GTM Implementation → QA & Validation
Business Context & The Platform Migration
SmileSellers (smilesellers.com) provides oral care and teeth alignment solutions. The business previously operated its digital storefront on Shopify, where I had built a stable, high-accuracy tracking architecture. To achieve greater design flexibility and custom backend workflows, the company executed a full platform migration from Shopify to WordPress WooCommerce alongside a brand-new website design. While the new storefront looked modern, the migration abruptly broke their entire analytics and marketing measurement infrastructure.
- Client: SmileSellers (smilesellers.com) — Direct-to-Consumer & B2B Oral Care Store
- Source Platform: Shopify (with native Liquid ecommerce DataLayer objects)
- Target Platform: WordPress WooCommerce (custom PHP theme + AJAX interactions)
- Business Impact: Zero ecommerce revenue, product impressions, or cart additions were reporting in GA4, paralyzing Google Ads Smart Bidding and Meta Advantage+ campaigns
Post-Migration Tracking Symptoms & Broken Data Pipelines
Following the migration launch, an immediate audit of GA4 and browser console network payloads revealed extensive telemetry failures:
- ✕ Disappearance of Liquid Data Layer: Shopify {{ content_for_header }} and custom liquid templates were completely absent on WordPress
- ✕ Broken AJAX Cart Tracking: The new WooCommerce theme added products to cart via jQuery AJAX without triggering standard page-reload DataLayer pushes
- ✕ Fragmented Checkout Funnel: WooCommerce multi-step checkout failed to fire begin_checkout, add_shipping_info, or add_payment_info events
- ✕ Missing Purchase Telemetry: The order-received thank you page lacked dynamic order data, customer value, tax, shipping, and item arrays
- ✕ Complete Loss of Micro-Conversions: Site searches, newsletter signups, contact inquiries, and direct phone/email contact clicks went unrecorded
Full-Funnel Ecommerce & Lead Event Specification Matrix
To establish complete measurement parity and capture every customer interaction, I engineered a standardized event contract across all ecommerce stages and lead touchpoints:
| GA4 Event Name | Internal Event / Trigger Name | Trigger Condition | Business Purpose & Parameters |
|---|---|---|---|
| page_view | page_view | Every page load | GA4 default page tracking, page_location, and title |
| view_item | product_viewed | Single product page view | Captures item_id, item_name, price, currency, category, variant |
| view_item_list | collection_viewed | Product category / archive view | Pushes item array with list_name and list_id index |
| add_to_cart | product_added_to_cart | Product added to cart (native or AJAX) | Captures added item, price, quantity, and currency |
| remove_from_cart | product_removed_from_cart | Item removed from cart / drawer | Tracks removed item_id, quantity, and remaining cart value |
| view_cart | cart_viewed | Cart page opened | Captures full cart items array, total value, and currency |
| begin_checkout | checkout_started | Checkout page loaded | Initializes checkout funnel with item array and value |
| add_shipping_info | checkout_shipping_info_submitted | Shipping tier / address selected | Captures shipping_tier and updated checkout value |
| add_payment_info | payment_info_submitted | Payment method chosen (Stripe/PayPal) | Captures payment_type (Credit Card, PayPal, COD) |
| purchase | checkout_completed | WooCommerce order-received page | Pushes transaction_id, value, tax, shipping, and purchased items |
| search | site_search_performed | Site search form query submitted | Pushes search_term keyword for internal search optimization |
| newsletter_signup | newsletter_submitted | Form with #newsletter_form submitted | Tracks newsletter subscriber opt-in conversions |
| contact_form_submit | contact_inquiry_submitted | Form with #contact_form submitted | Tracks customer inquiry and B2B lead generation |
| phone_number_click | phone_intent_click | Click on tel: link | Tracks high-intent phone call interactions |
| email_click | email_intent_click | Click on mailto: link | Tracks direct email inquiry interactions |
Engineering the WooCommerce DataLayer Architecture
I implemented a hybrid architecture combining server-side PHP WordPress hooks for deterministic page-load events with client-side JavaScript listeners for dynamic interactions:
- Server-Side PHP Hooks: Utilized woocommerce_before_single_product, woocommerce_archive_description, and woocommerce_thankyou in child theme functions.php to inject pure JSON Data Layer scripts before GTM container execution
- AJAX Add-to-Cart Interception: Attached jQuery event listeners to $(document.body).on("added_to_cart") and mini-cart DOM mutations to intercept asynchronous item additions
- Checkout Step Orchestration: Hooked into WooCommerce checkout AJAX validation events to fire add_shipping_info and add_payment_info deterministically as users progressed through the order form
Micro-Conversions & Business Intent Telemetry
In addition to the core transaction funnel, I instrumented critical engagement and contact micro-conversions:
- Site Search: Listened for WooCommerce search submissions and extracted the s query parameter, passing search_term to GA4 search reports
- Newsletter Opt-in: Intercepted #newsletter_form submissions, verified successful AJAX responses, and dispatched newsletter_signup events
- Contact Forms: Monitored #contact_form submissions with form field validation to eliminate false positives on failed inputs
- Click-to-Call & Click-to-Email: Global event delegates capturing clicks on a[href^="tel:"] and a[href^="mailto:"] with destination numbers and addresses
Transaction Deduplication & Validation Protocol
To prevent duplicate purchase events when customers refresh their WooCommerce order confirmation page or reopen browser tabs, I engineered a sessionStorage deduplication lock:
- GTM Deduplication Variable: Reads transaction_id from the DataLayer and checks sessionStorage.getItem("purchased_" + transaction_id)
- Execution Lock: If already marked as processed, the purchase tag is blocked; otherwise, the flag is recorded and the event is dispatched
- GA4 DebugView & BigQuery QA: Verified all 15 events in GA4 DebugView and validated transaction totals against WooCommerce store database records
Business Outcome & Campaign Recovery
The custom WooCommerce Data Layer and GTM architecture fully restored SmileSellers' measurement ecosystem:
- 100% Ecommerce Funnel Visibility: Seamless tracking from view_item through purchase across all device types
- 0% Duplicate Transactions: Eliminated ghost revenue spikes from thank-you page reloads
- Ad Campaign Optimization Restored: Google Ads and Meta Ads conversion tracking received accurate revenue signals within 24 hours of deployment
- Complete Lead Telemetry: Captured thousands of monthly newsletter signups, searches, and direct contact intents
Diagnostic Code & Query Architecture:
// In child theme functions.php: WooCommerce Thank You Page Data Layer
add_action('woocommerce_thankyou', 'smilesellers_purchase_datalayer', 10, 1);
function smilesellers_purchase_datalayer($order_id) {
if (!$order_id) return;
$order = wc_get_order($order_id);
if (!$order) return;
$items = array();
foreach ($order->get_items() as $item_id => $item) {
$product = $item->get_product();
$items[] = array(
'item_id' => (string)$item->get_product_id(),
'item_name' => $item->get_name(),
'price' => (float)$order->get_item_total($item, false, false),
'quantity' => (int)$item->get_quantity()
);
}
$ecommerce_data = array(
'event' => 'purchase',
'ecommerce' => array(
'transaction_id' => (string)$order->get_order_number(),
'value' => (float)$order->get_total(),
'tax' => (float)$order->get_total_tax(),
'shipping' => (float)$order->get_shipping_total(),
'currency' => $order->get_currency(),
'items' => $items
)
);
echo '<script>window.dataLayer = window.dataLayer || []; window.dataLayer.push(' . json_encode($ecommerce_data) . ');</script>';
}
// Client-side AJAX Add to Cart & Micro-Conversion Listeners
(function($) {
'use strict';
window.dataLayer = window.dataLayer || [];
// 1. Intercept WooCommerce AJAX Add to Cart
$(document.body).on('added_to_cart', function(event, fragments, cart_hash, $button) {
var productId = $button.data('product_id') || $button.val();
var productName = $button.closest('.product, .product-card').find('.woocommerce-loop-product__title').text().trim();
var price = parseFloat($button.data('price') || 0);
window.dataLayer.push({
event: 'add_to_cart',
ecommerce: {
currency: 'USD',
value: price,
items: [{
item_id: String(productId),
item_name: productName || 'Product #' + productId,
price: price,
quantity: 1
}]
}
});
});
// 2. Track Phone Intent Clicks
$(document).on('click', 'a[href^="tel:"]', function() {
window.dataLayer.push({
event: 'phone_number_click',
phone_number: $(this).attr('href').replace('tel:', '')
});
});
// 3. Track Email Intent Clicks
$(document).on('click', 'a[href^="mailto:"]', function() {
window.dataLayer.push({
event: 'email_click',
email_address: $(this).attr('href').replace('mailto:', '')
});
});
// 4. Track Newsletter Opt-ins
$('#newsletter_form').on('submit', function() {
window.dataLayer.push({
event: 'newsletter_signup',
form_id: 'newsletter_form'
});
});
})(jQuery);
SELECT
event_name,
COUNT(DISTINCT user_pseudo_id) AS unique_users,
COUNT(1) AS total_events,
ROUND(SUM(COALESCE(event_value_in_usd, 0)), 2) AS total_revenue_usd
FROM
`smilesellers-analytics.analytics_123456789.events_*`
WHERE
_TABLE_SUFFIX BETWEEN '20250101' AND '20250131'
AND event_name IN (
'view_item',
'add_to_cart',
'view_cart',
'begin_checkout',
'add_shipping_info',
'add_payment_info',
'purchase'
)
GROUP BY
event_name
ORDER BY
total_events DESC;