Skip to content

Direct GA4 Integration (No GTM)

This guide shows how to send Campaign Cart SDK events directly to Google Analytics 4 without using Google Tag Manager by using an event transformer script.

Use the GA4 Bridge when:

  • You want to send events directly to GA4 without GTM
  • You’re already using GA4 and don’t want to set up GTM
  • You need simpler setup with fewer moving parts
  • You want to avoid GTM’s additional layer
  1. Add Google Analytics 4

    Add the GA4 tracking code to your page:

    <!-- Google Analytics 4 -->
    <script async src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXXXX"></script>
    <script>
    window.dataLayer = window.dataLayer || [];
    function gtag(){dataLayer.push(arguments);}
    gtag('js', new Date());
    gtag('config', 'G-XXXXXXXXXX');
    </script>

    Replace G-XXXXXXXXXX with your GA4 Measurement ID.

  2. Add the Bridge Script

    Add the NextDataLayer GA4 Bridge script after the Campaign Cart SDK:

    <!-- Campaign Cart SDK -->
    <script src="https://cdn.jsdelivr.net/gh/NextCommerceCo/[email protected]/dist/loader.js" type="module"></script>
    <!-- NextDataLayer GA4 Bridge -->
    <script src="/path/to/NextDataLayer_GA4.js"></script>
  3. Enable Analytics in SDK Config

    window.nextConfig = {
    apiKey: 'your-api-key',
    analytics: {
    enabled: true,
    mode: 'auto'
    }
    };

That’s it! Events will automatically flow from Campaign Cart SDK → NextDataLayer → GA4.

The bridge script:

  1. Waits for NextDataLayer to be available
  2. Intercepts events pushed to window.NextDataLayer
  3. Converts event names from dl_* format to standard GA4 format
  4. Formats ecommerce data according to GA4 specification
  5. Pushes to window.dataLayer for GA4 to consume
  6. Prevents duplicates using event deduplication
  7. Handles upsells by converting them to purchase events
Campaign Cart SDK Event
NextDataLayer (dl_add_to_cart)
GA4 Bridge Script (converts)
window.dataLayer (add_to_cart)
Google Analytics 4

The bridge automatically converts SDK events to GA4 standard events:

SDK EventGA4 EventDescription
dl_view_itemview_itemProduct viewed
dl_view_item_listview_item_listProduct list viewed
dl_add_to_cartadd_to_cartItem added to cart
dl_remove_from_cartremove_from_cartItem removed from cart
dl_view_cartview_cartCart page viewed
dl_begin_checkoutbegin_checkoutCheckout started
dl_add_shipping_infoadd_shipping_infoShipping info added
dl_add_payment_infoadd_payment_infoPayment info added
dl_purchasepurchaseOrder completed
SDK EventGA4 EventSpecial Handling
dl_viewed_upsellview_itemUpsell viewed as product view
dl_accepted_upsellpurchaseUpsell converted to purchase with same transaction_id
SDK EventGA4 Event
dl_sign_upsign_up
dl_loginlogin

The bridge tracks processed events and prevents duplicates:

// Events are deduplicated using:
// - Event name
// - Sequence number
// - Item ID
// - Order ID

Upsells are automatically converted to purchase events with the original transaction ID:

// Original purchase
{
event: 'purchase',
transaction_id: 'ORD-12345',
value: 99.99
}
// Upsell (automatically uses same transaction_id)
{
event: 'purchase',
transaction_id: 'ORD-12345', // Same as original!
value: 29.99,
items: [{ item_category: 'Upsell', ... }]
}

This allows GA4 to track total order value including upsells.

The bridge follows GA4 best practices by clearing the ecommerce object before each event:

// Clear ecommerce first
window.dataLayer.push({ ecommerce: null });
// Then push new event
window.dataLayer.push({
event: 'add_to_cart',
ecommerce: { ... }
});

Automatically cleans up old data to prevent memory leaks:

  • Keeps last 500 processed events
  • Keeps last 50 transaction IDs
  • Runs cleanup every 60 seconds

Debug logging is automatically enabled on:

  • localhost
  • URLs with ?debug=true

View logs in browser console:

[GA4 Bridge] Initializing dataLayer bridge
[GA4 Bridge] Converted dl_add_to_cart → add_to_cart
[GA4 Bridge] Upsell converted to purchase with transaction_id: ORD-12345

The bridge exposes debugging utilities on window.GA4Bridge:

const count = window.GA4Bridge.getProcessedCount();
console.log(`Processed ${count} events`);
const mapping = window.GA4Bridge.getEventMap();
console.log(mapping);
// {
// 'dl_add_to_cart': 'add_to_cart',
// 'dl_purchase': 'purchase',
// ...
// }
const transactions = window.GA4Bridge.getTransactionMap();
console.log(transactions);
// {
// '123': { transaction_id: 'ORD-12345', order_number: '12345' },
// ...
// }
const isActive = window.GA4Bridge.isActive();
console.log(`Bridge active: ${isActive}`);

SDK fires:

window.NextDataLayer.push({
event: 'dl_add_to_cart',
event_id: '1234567890_abc123',
ecommerce: {
currency: 'USD',
value: 99.99,
items: [{
item_id: 'SKU-123',
item_name: 'Product Name',
price: 99.99,
quantity: 1
}]
}
});

Bridge converts to:

window.dataLayer.push({ ecommerce: null }); // Clear first
window.dataLayer.push({
event: 'add_to_cart',
event_id: '1234567890_abc123',
ecommerce: {
currency: 'USD',
value: 99.99,
items: [{
item_id: 'SKU-123',
item_name: 'Product Name',
price: 99.99,
quantity: 1
}]
}
});

SDK fires:

window.NextDataLayer.push({
event: 'dl_purchase',
ecommerce: {
transaction_id: 'ORD-12345',
order_number: '12345',
value: 159.99,
currency: 'USD',
tax: 9.99,
shipping: 10.00,
items: [...]
}
});

Bridge stores transaction ID and converts to:

window.dataLayer.push({ ecommerce: null });
window.dataLayer.push({
event: 'purchase',
ecommerce: {
transaction_id: 'ORD-12345',
order_number: '12345',
value: 159.99,
currency: 'USD',
tax: 9.99,
shipping: 10.00,
items: [...]
}
});

SDK fires:

window.NextDataLayer.push({
event: 'dl_accepted_upsell',
order_id: '123',
upsell: {
package_id: 'warranty-extended',
package_name: 'Extended Warranty',
price: 29.99,
quantity: 1
}
});

Bridge converts to:

window.dataLayer.push({ ecommerce: null });
window.dataLayer.push({
event: 'purchase', // Converted to purchase!
ecommerce: {
transaction_id: 'ORD-12345', // Same as original purchase
order_number: '12345',
value: 29.99,
currency: 'USD',
items: [{
item_id: 'warranty-extended',
item_name: 'Extended Warranty',
price: 29.99,
quantity: 1,
item_category: 'Upsell'
}]
}
});
  1. Check GA4 is loaded

    console.log(typeof gtag); // Should be 'function'
  2. Check bridge is active

    console.log(window.GA4Bridge.isActive()); // Should be true
  3. Check events are being converted

    • Open browser console
    • Add ?debug=true to URL
    • Look for [GA4 Bridge] log messages
  4. Verify NextDataLayer exists

    console.log(window.NextDataLayer); // Should be an array

The bridge automatically prevents duplicates, but if you see duplicates:

  1. Make sure you’re only loading the bridge script once
  2. Check that you’re not also pushing events to window.dataLayer manually
  3. Verify the bridge is loaded after the SDK

If upsells aren’t showing up as purchases:

  1. Check transaction map

    console.log(window.GA4Bridge.getTransactionMap());
  2. Verify purchase event fired first

    • The initial purchase must fire before upsells
    • Transaction ID is stored from the purchase event
  3. Check order_id is present

    • Upsell events need order_id to look up transaction_id

If upsells show undefined transaction_id:

  • The initial dl_purchase event must include ecommerce.transaction_id
  • The upsell event must include order_id matching the purchase
  • Check the transaction map: window.GA4Bridge.getTransactionMap()

The bridge is lightweight and efficient:

  • ~5KB minified
  • Processes events in <1ms
  • Memory-safe with automatic cleanup
  • No external dependencies
FeatureGA4 BridgeGoogle Tag Manager
Setup complexitySimple (1 script)Complex (container setup)
Event conversionAutomaticManual triggers/tags
Upsell handlingBuilt-inManual configuration
DeduplicationAutomaticMust configure
Additional trackingLimitedUnlimited
Tag managementNoneFull tag management
Best forDirect GA4Multiple platforms

Don’t use the GA4 Bridge if:

  • You’re already using GTM successfully
  • You need to send data to multiple platforms (Facebook, TikTok, etc.)
  • You need complex tag management and triggering
  • You want to manage tags without code deployments

In these cases, use Google Tag Manager instead. See Google Tag Manager Setup.

Want to adapt this pattern for other platforms (TikTok, Snapchat, Pinterest)?

See Event Transformers for:

  • Generic transformer template
  • Examples for TikTok, Snapchat, Pinterest
  • Multi-platform routing
  • Best practices and patterns