
Analytics Integration
- 90 installs
- 41 repo stars
- Updated March 13, 2026
- finsilabs/awesome-ecommerce-skills
Implement GA4, Meta Pixel, and server-side GTM tagging with a proper data layer to capture accurate ecommerce conversion events for ad campaigns.
About
A skill for building an ecommerce analytics stack with GA4, Meta Pixel, and server-side GTM tagging on a structured data layer. A developer uses it to capture reliable conversion events for ad attribution.
- Structured data layer for product and checkout events
- Server-side tagging and Meta Conversions API for accuracy
Analytics Integration by the numbers
- 90 all-time installs (skills.sh)
- Ranked #1,160 of 1,879 Marketing & SEO skills by installs in the Skillselion catalog
- Data as of Aug 3, 2026 (Skillselion catalog sync)
npx skills add https://github.com/finsilabs/awesome-ecommerce-skills --skill analytics-integrationAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 90 |
|---|---|
| repo stars | ★ 41 |
| Last updated | March 13, 2026 |
| Repository | finsilabs/awesome-ecommerce-skills ↗ |
What it does
Implement GA4, Meta Pixel, and server-side GTM tagging with a proper data layer to capture accurate ecommerce conversion events for ad campaigns.
Files
Analytics Integration
Overview
Implement a robust analytics stack for e-commerce using Google Analytics 4 (GA4), Meta Pixel, and Google Tag Manager (GTM). Covers structured data layer design for product and checkout events, server-side tagging via GTM server containers to improve data accuracy and bypass browser restrictions, and Meta Conversions API for reliable ad attribution.
When to Use This Skill
- When adding GA4 e-commerce tracking (product views, add-to-cart, checkout steps, purchase) to a new or existing store
- When implementing Meta Pixel alongside the Conversions API for dual-mode event delivery to improve ad attribution
- When migrating a GTM web container to a server-side container for better data control and cookie lifespans
- When troubleshooting missing or duplicate conversion events caused by ad blockers or client-side failures
- When meeting privacy requirements that mandate server-side deduplication between browser and server events
Core Instructions
Step 1: Determine your platform and recommended approach
| Platform | Recommended Analytics Setup | Key Actions |
|---|---|---|
| Shopify | Built-in GA4 integration + GTM app | Connect GA4 in Online Store → Preferences → Google Analytics; install GTM4WP or the official Google & YouTube channel app for Meta Pixel |
| WooCommerce | MonsterInsights plugin for GA4 + GTM | Install MonsterInsights (free tier or Pro from $99/yr) for GA4; install WooCommerce Google Analytics Integration (free) for enhanced e-commerce events |
| BigCommerce | Native GA4 integration + channel manager | Connect GA4 in Advanced Settings → Data Solutions → Google Analytics; use BigCommerce's Meta Pixel integration under the Channel Manager |
| Custom / Headless | GTM container + server-side container + custom data layer | Implement a canonical data layer, deploy GTM server container on Cloud Run or Vercel, and add Meta Conversions API from your backend |
Step 2: Platform-specific analytics setup
---
Shopify
Connect GA4 (built-in, no code required):
1. Go to Online Store → Preferences → Google Analytics 2. Click Connect your Google account and select your GA4 property 3. Shopify sends all standard e-commerce events automatically: page_view, view_item, add_to_cart, begin_checkout, purchase 4. In Google Analytics → Configure → Events, mark the purchase event as a conversion
Track checkout funnel with GA4 Explorations:
1. In GA4, go to Explore → Funnel exploration 2. Create funnel steps:
- Step 1:
begin_checkoutevent - Step 2:
add_shipping_infoevent - Step 3:
add_payment_infoevent - Step 4:
purchaseevent
3. This shows exactly where shoppers drop off in checkout
Add Meta Pixel:
1. Go to the Shopify App Store and install the Meta channel app (free) 2. Follow the setup wizard to connect your Facebook Business account 3. Meta sends pixel events automatically through the Shopify integration, including the Conversions API for server-side deduplication
---
WooCommerce
Install MonsterInsights for GA4:
1. Install MonsterInsights from wordpress.org (free tier available; Pro from $99/year adds enhanced e-commerce) 2. Go to Insights → Settings → General and connect your GA4 property 3. Enable Enhanced eCommerce Tracking in the MonsterInsights settings — this sends add_to_cart, begin_checkout, and purchase events to GA4 with product-level data
Add Meta Pixel:
1. Install PixelYourSite (free tier available, pro from $69/year) from wordpress.org 2. Enter your Pixel ID and connect via Facebook's Business Integration 3. PixelYourSite includes the WooCommerce extension for ViewContent, AddToCart, InitiateCheckout, and Purchase events
Verify events are firing:
1. Install the Meta Pixel Helper Chrome extension 2. Visit your product page and checkout — the extension shows which events fire on each page 3. In GA4, use Admin → DebugView to confirm events arrive in real time
---
BigCommerce
Connect GA4 (built-in):
1. Go to Advanced Settings → Data Solutions → Google Analytics 2. Enter your GA4 Measurement ID (format: G-XXXXXXXX) 3. BigCommerce fires e-commerce events automatically including purchase with full order data
Add Meta Pixel:
1. Go to Channel Manager → Marketplace → Meta 2. Connect your Facebook Business account 3. BigCommerce sends Pixel events natively and supports the Conversions API for server-side deduplication
---
Custom / Headless
Design a canonical data layer first — every event follows the same shape regardless of which vendor consumes it:
// dataLayer must be initialized in <head> before GTM loads
window.dataLayer = window.dataLayer || [];
// Always clear ecommerce before pushing a new event (prevents GTM from merging stale items)
window.dataLayer.push({ ecommerce: null });
window.dataLayer.push({
event: 'add_to_cart',
ecommerce: {
currency: 'USD',
value: product.price * quantity,
items: [{
item_id: product.sku,
item_name: product.name,
item_brand: product.brand,
item_category: product.category,
price: product.price,
quantity,
}],
},
});Purchase event (fire after order confirmed, use server-generated order ID as `transaction_id`):
window.dataLayer.push({ ecommerce: null });
window.dataLayer.push({
event: 'purchase',
ecommerce: {
transaction_id: order.id, // Must be unique — deduplicate browser + server events
value: order.total,
tax: order.tax,
shipping: order.shippingCost,
currency: order.currency,
coupon: order.couponCode || '',
items: order.lineItems.map(line => ({
item_id: line.sku,
item_name: line.name,
price: line.unitPrice,
quantity: line.qty,
})),
},
});Meta Pixel with Conversions API deduplication — send the same event from browser and server using a shared event_id:
// Browser — pass event_id for deduplication
const eventId = `purchase_${order.id}`;
fbq('track', 'Purchase', {
value: order.total,
currency: order.currency,
content_ids: order.lineItems.map(l => l.sku),
content_type: 'product',
}, { eventID: eventId });
// Send eventId to server for the Conversions API mirror
await fetch('/api/analytics/meta-purchase', {
method: 'POST',
body: JSON.stringify({ orderId: order.id, eventId }),
});// Server-side Conversions API (Node.js)
import { ServerEvent, EventRequest, UserData, CustomData } from 'facebook-nodejs-business-sdk';
export async function sendMetaPurchase(order, eventId, userAgent, ipAddress) {
const userData = new UserData()
.setEmail(order.customerEmail) // Automatically hashed by SDK
.setClientIpAddress(ipAddress)
.setClientUserAgent(userAgent);
const customData = new CustomData()
.setValue(order.total)
.setCurrency(order.currency)
.setContentIds(order.lineItems.map(l => l.sku));
const serverEvent = new ServerEvent()
.setEventName('Purchase')
.setEventTime(Math.floor(Date.now() / 1000))
.setUserData(userData)
.setCustomData(customData)
.setEventId(eventId) // Matches browser eventID — Meta deduplicates automatically
.setActionSource('website');
await new EventRequest(process.env.META_ACCESS_TOKEN, process.env.META_PIXEL_ID)
.setEvents([serverEvent])
.execute();
}Initialize Google Consent Mode v2 (required for EU traffic as of March 2024):
// Must run BEFORE GTM or gtag.js loads
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('consent', 'default', {
ad_storage: 'denied',
ad_user_data: 'denied',
ad_personalization: 'denied',
analytics_storage: 'denied',
wait_for_update: 500, // Wait for CMP to update consent
});
// After user accepts cookies via your CMP:
gtag('consent', 'update', {
ad_storage: preferences.marketing ? 'granted' : 'denied',
ad_user_data: preferences.marketing ? 'granted' : 'denied',
analytics_storage: preferences.analytics ? 'granted' : 'denied',
});Validate events before deploying:
# GA4 Measurement Protocol validation (returns hit validation report)
curl -X POST \
"https://www.google-analytics.com/debug/mp/collect?measurement_id=G-XXXXXXXX&api_secret=YOUR_SECRET" \
-H "Content-Type: application/json" \
-d '{"client_id":"test-123","events":[{"name":"purchase","params":{"transaction_id":"T-001","value":59.99,"currency":"USD"}}]}'Best Practices
- Clear `ecommerce: null` before every e-commerce push — GTM merges data layer objects, so stale item arrays from a previous event will contaminate the next one
- Use your server-generated order ID as `transaction_id` — never generate it on the client; this ensures deduplication works when both browser and server events fire
- Send Conversions API events from a post-payment webhook — webhook delivery is more reliable than the client completing a fetch call during checkout
- Gate `purchase` events behind idempotency checks — store fired
transaction_idvalues in sessionStorage and skip re-firing if the confirmation page is reloaded - Use GTM environments for staging — test GTM changes in a staging environment so QA traffic never pollutes live reports
Common Pitfalls
| Problem | Solution |
|---|---|
| Duplicate purchase events in GA4 | Check sessionStorage.getItem('purchase_fired_' + order.id) before pushing; set it after the push fires |
| Items array empty in GTM | Forgot to push { ecommerce: null } before the event — GTM caches the previous items array |
| Meta Pixel and Conversions API both counting conversions | Pass matching eventID (browser) and event_id (server) — Meta deduplicates on this field |
| Shopify GA4 funnel shows no data | Verify the GA4 Measurement ID in Online Store → Preferences matches your property; check GA4 DebugView to confirm events are firing |
| MonsterInsights not tracking WooCommerce orders | Ensure the Pro license is active (Enhanced eCommerce requires Pro); clear any caching plugins after installation |
Related Skills
- @webhook-architecture
- @erp-integration
- @email-service-integration
- @gdpr-ecommerce
{
"context": "Tests whether the agent implements a canonical, GTM-compatible e-commerce data layer with correct event patterns — including stale-data prevention, GA4-standard field naming, server-side transaction ID usage, and purchase deduplication via sessionStorage.",
"type": "weighted_checklist",
"checklist": [
{
"name": "dataLayer initialization",
"max_score": 5,
"description": "Uses `window.dataLayer = window.dataLayer || []` to initialize the dataLayer (not just assigning an empty array unconditionally)"
},
{
"name": "ecommerce null before view_item_list",
"max_score": 8,
"description": "Pushes `{ ecommerce: null }` to dataLayer immediately before the view_item_list event push"
},
{
"name": "ecommerce null before view_item",
"max_score": 8,
"description": "Pushes `{ ecommerce: null }` to dataLayer immediately before the view_item event push"
},
{
"name": "ecommerce null before add_to_cart",
"max_score": 8,
"description": "Pushes `{ ecommerce: null }` to dataLayer immediately before the add_to_cart event push"
},
{
"name": "ecommerce null before purchase",
"max_score": 8,
"description": "Pushes `{ ecommerce: null }` to dataLayer immediately before the purchase event push"
},
{
"name": "GA4 item field names",
"max_score": 10,
"description": "Uses GA4-standard field names in items arrays: item_id, item_name, item_brand, item_category (not custom names like productId, productName, etc.)"
},
{
"name": "currency field present",
"max_score": 5,
"description": "Includes a currency field in the ecommerce object for each event"
},
{
"name": "transaction_id from server",
"max_score": 10,
"description": "purchase event uses order.id (the server-generated order ID) as transaction_id — does NOT compute or generate a transaction ID on the client side"
},
{
"name": "sessionStorage read before purchase",
"max_score": 14,
"description": "trackPurchase checks sessionStorage (e.g., sessionStorage.getItem(...)) for a key based on the order ID before pushing the purchase event"
},
{
"name": "sessionStorage write after purchase",
"max_score": 14,
"description": "trackPurchase sets a sessionStorage entry (e.g., sessionStorage.setItem(...)) after pushing the purchase event to prevent re-firing"
},
{
"name": "vendor-neutral dataLayer",
"max_score": 10,
"description": "The dataLayer pushes use GA4 naming conventions (event names like view_item, add_to_cart, purchase and item fields like item_id). Does NOT include vendor-specific transforms (e.g., Meta pixel field names) inside the dataLayer push"
}
]
}
E-commerce Analytics Tracking Implementation
Problem/Feature Description
ShopBright, a mid-size online retailer, recently completed a full redesign of their storefront but stripped out all legacy analytics code in the process. The head of growth has asked the engineering team to implement proper e-commerce tracking before the relaunch. They need visibility into how customers browse products and move through the funnel — from seeing items on a listing page all the way to confirming a purchase.
The engineering team uses Google Tag Manager to manage analytics tags and wants all tracking data routed through a single dataLayer that GTM can consume. The codebase is plain JavaScript (no bundler required), but the implementation should be structured so that another developer can easily add tracking for new events later. The business also cares deeply about data accuracy: there have been reports of duplicate purchase events at other companies, and the team wants to make sure this won't be a problem at ShopBright.
Output Specification
Produce a single JavaScript file named tracking.js that implements the following:
- A
trackViewItemList(products)function for the product listing page - A
trackViewItem(product)function for the product detail page - A
trackAddToCart(product, quantity)function - A
trackPurchase(order)function for the order confirmation page
The order object passed to trackPurchase will contain the following fields:
id— the server-generated order IDtotal,tax,shippingCost,currency,couponCodelineItems— array of{ sku, name, unitPrice, qty }
The product object contains: sku, name, brand, category, price.
Also produce a short README.md explaining the idempotency strategy used for the purchase event, and any important assumptions.
{
"context": "Tests whether the agent implements Meta Pixel and Conversions API with correct deduplication via matching event IDs, uses the official facebook-nodejs-business-sdk for server-side sending, keeps PII out of browser-side events, and routes the server call through a webhook for reliability.",
"type": "weighted_checklist",
"checklist": [
{
"name": "facebook-nodejs-business-sdk import",
"max_score": 12,
"description": "server-handler.js imports from 'facebook-nodejs-business-sdk' (e.g., ServerEvent, EventRequest, UserData, CustomData) — does NOT use a raw fetch/axios call to the Conversions API endpoint directly"
},
{
"name": "browser eventID passed to fbq",
"max_score": 12,
"description": "browser-tracking.js passes an eventID as the fourth argument to fbq('track', 'Purchase', ..., { eventID: <value> })"
},
{
"name": "server setEventId called",
"max_score": 12,
"description": "server-handler.js calls .setEventId(...) on the ServerEvent instance"
},
{
"name": "matching event IDs",
"max_score": 10,
"description": "The eventID in browser-tracking.js and the value passed to .setEventId() in server-handler.js are derived from the same source (e.g., both based on order.id), ensuring they will match for the same purchase"
},
{
"name": "UserData SDK for PII",
"max_score": 10,
"description": "server-handler.js uses new UserData().setEmail(order.customerEmail) (or equivalent SDK method) rather than manually hashing and passing raw values"
},
{
"name": "no raw PII in browser call",
"max_score": 10,
"description": "browser-tracking.js does NOT include raw email, phone number, first name, or last name in the data object passed to fbq()"
},
{
"name": "webhook rationale documented",
"max_score": 12,
"description": "ARCHITECTURE.md (or code comments) explains that the server-side Conversions API call is made from a webhook handler rather than directly from the purchase API response, with reasoning about reliability"
},
{
"name": "action_source set",
"max_score": 6,
"description": "server-handler.js calls .setActionSource('website') on the ServerEvent"
},
{
"name": "event_time set",
"max_score": 6,
"description": "server-handler.js sets event time as a Unix timestamp (e.g., Math.floor(Date.now() / 1000))"
},
{
"name": "credentials from environment",
"max_score": 5,
"description": "server-handler.js reads Meta access token and pixel ID from environment variables (process.env.META_ACCESS_TOKEN and process.env.META_PIXEL_ID or equivalent), not hardcoded"
},
{
"name": "content_ids in CustomData",
"max_score": 5,
"description": "server-handler.js includes content_ids (SKUs from order.lineItems) in the CustomData sent to Meta"
}
]
}
Meta Ad Conversion Tracking with Server-Side Reliability
Problem/Feature Description
LunaGear, a direct-to-consumer outdoor equipment brand, runs large acquisition campaigns on Meta (Facebook and Instagram). Their marketing team has noticed significant under-reporting of purchase conversions in Meta Ads Manager — ad blockers and iOS privacy restrictions are preventing the browser-based Meta Pixel from firing reliably. The attribution gap is causing the bidding algorithm to underperform because it sees fewer conversions than actually occurred.
The engineering team has been asked to add a server-side complement to the existing browser pixel that will catch conversions even when the browser event fails to send. A critical requirement is that the same purchase conversion must NOT be counted twice in Meta — once from the browser and once from the server. The engineering team uses Node.js on the backend. They also handle customer data including email addresses, and the legal team has flagged that personally identifiable information must not be transmitted in raw form to any third-party analytics service via the browser.
Output Specification
Produce two files:
1. browser-tracking.js — client-side JavaScript that fires the Meta Pixel purchase event when an order is confirmed. The order object available in scope contains: id, total, currency, lineItems (array of { sku }).
2. server-handler.js — a Node.js module that exports an async function sendPurchaseConversion(order, requestContext). The requestContext object contains ipAddress and userAgent. The order object contains: id, total, currency, lineItems (array of { sku }), and customerEmail. The function should send the purchase event to Meta using the server-side API.
Also produce a short ARCHITECTURE.md explaining how the two files work together to achieve reliable conversion tracking and avoid double-counting.
{
"context": "Tests whether the agent correctly deduplicates purchase events using sessionStorage, implements proper SPA page view tracking via route change detection, and extracts the GA4 client ID from the _ga cookie for server-side Measurement Protocol calls.",
"type": "weighted_checklist",
"checklist": [
{
"name": "sessionStorage read before purchase",
"max_score": 14,
"description": "purchase-tracking.js reads from sessionStorage (e.g., sessionStorage.getItem(...)) using a key that includes the order ID before pushing the purchase event"
},
{
"name": "sessionStorage write after purchase",
"max_score": 14,
"description": "purchase-tracking.js calls sessionStorage.setItem(...) with an order-specific key after pushing the purchase event to prevent re-firing on reload"
},
{
"name": "ecommerce null before purchase",
"max_score": 8,
"description": "purchase-tracking.js pushes { ecommerce: null } to window.dataLayer before pushing the purchase event"
},
{
"name": "route change detection",
"max_score": 14,
"description": "spa-pageview.js listens for client-side navigation events (e.g., popstate, hashchange, or Next.js router events) rather than only firing on the initial script load"
},
{
"name": "page_view fires on navigation",
"max_score": 10,
"description": "spa-pageview.js pushes a page_view (or equivalent) event to dataLayer on each route change, not only once when the script first loads"
},
{
"name": "History Change GTM note",
"max_score": 10,
"description": "spa-pageview.js includes a comment explaining that a History Change trigger (or equivalent) must be configured in GTM, and that the default All Pages trigger alone is insufficient for SPA navigation"
},
{
"name": "_ga cookie parsing",
"max_score": 15,
"description": "ga4-server.js extracts client_id from the _ga cookie by splitting on '.' and taking the last two segments (e.g., cookies._ga.split('.').slice(2).join('.') or equivalent)"
},
{
"name": "client_id in Measurement Protocol payload",
"max_score": 10,
"description": "ga4-server.js includes the extracted client_id value in the request body sent to the GA4 Measurement Protocol endpoint"
},
{
"name": "GA4 Measurement Protocol endpoint",
"max_score": 5,
"description": "ga4-server.js sends a POST request to https://www.google-analytics.com/mp/collect (not the debug endpoint) with measurement_id and api_secret from environment variables"
}
]
}
Fixing Broken Analytics in a Next.js Storefront
Problem/Feature Description
Verdant Shop, a plant retailer, runs a Next.js single-page application storefront. Since migrating to the SPA architecture six months ago, two analytics problems have emerged that are distorting their reporting:
Problem 1 — Duplicate purchases: GA4 reports are showing each purchase two to three times. The engineering team traced this to customers sharing their order confirmation URL with family members, and customers themselves refreshing the confirmation page. Every time the page loads, a purchase event fires, causing inflated revenue figures in GA4.
Problem 2 — Missing page views: GA4 shows almost no page view events after the initial session landing page. Because the app navigates client-side using Next.js router, subsequent page loads never trigger a full browser navigation, so GTM's default All Pages trigger only fires once per session.
The analytics team is also planning to add a server-side GA4 fallback for purchases (in case the browser event fails). They need the Node.js backend to identify the correct GA4 client ID for the user so that server-side events are attributed correctly.
Output Specification
Produce the following files:
1. purchase-tracking.js — client-side JavaScript with a trackPurchase(order) function that fires the GA4 purchase event but handles the duplicate-event scenario. The order object contains: id, total, tax, shippingCost, currency, couponCode, lineItems (array of { sku, name, unitPrice, qty }).
2. spa-pageview.js — client-side JavaScript that tracks page views correctly in a Next.js / SPA environment. Include a comment explaining what GTM configuration is required to work alongside this code.
3. ga4-server.js — a Node.js module exporting an async function sendGA4Purchase(order, cookies) where cookies is the parsed cookie object from the HTTP request. The function should send a purchase event to GA4 via the Measurement Protocol, extracting the correct client identifier from the cookies. Use environment variables for GA4 credentials.
Also produce a FIXES.md summarising what caused each problem and how each fix addresses it.
{
"name": "finsi/analytics-integration",
"version": "0.1.0",
"summary": "GA4, Meta Pixel, server-side tagging, and data layer implementation",
"skills": {
"analytics-integration": {
"path": "SKILL.md"
}
}
}