Direct GA4 Integration (No GTM)
Send events directly to Google Analytics 4 without using Google Tag Manager using an event transformer.
Overview
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.
This is a specific implementation of the general Event Transformers pattern. You can use the same approach for TikTok, Snapchat, Pinterest, or any other platform.
When to Use This
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
If you're using Google Tag Manager, you don't need this script - the SDK already pushes events to window.dataLayer that GTM can consume.
Setup
-
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-XXXXXXXXXXwith your GA4 Measurement ID. -
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> -
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.
How It Works
The bridge script:
- Waits for NextDataLayer to be available
- Intercepts events pushed to
window.NextDataLayer - Converts event names from
dl_*format to standard GA4 format - Formats ecommerce data according to GA4 specification
- Pushes to window.dataLayer for GA4 to consume
- Prevents duplicates using event deduplication
- Handles upsells by converting them to purchase events
Event Conversion Flow
Campaign Cart SDK Event
↓
NextDataLayer (dl_add_to_cart)
↓
GA4 Bridge Script (converts)
↓
window.dataLayer (add_to_cart)
↓
Google Analytics 4Event Mapping
The bridge automatically converts SDK events to GA4 standard events:
E-commerce Events
| SDK Event | GA4 Event | Description |
|---|---|---|
dl_view_item | view_item | Product viewed |
dl_view_item_list | view_item_list | Product list viewed |
dl_add_to_cart | add_to_cart | Item added to cart |
dl_remove_from_cart | remove_from_cart | Item removed from cart |
dl_view_cart | view_cart | Cart page viewed |
dl_begin_checkout | begin_checkout | Checkout started |
dl_add_shipping_info | add_shipping_info | Shipping info added |
dl_add_payment_info | add_payment_info | Payment info added |
dl_purchase | purchase | Order completed |
Upsell Events
| SDK Event | GA4 Event | Special Handling |
|---|---|---|
dl_viewed_upsell | view_item | Upsell viewed as product view |
dl_accepted_upsell | purchase | Upsell converted to purchase with same transaction_id |
Upsell events are converted to purchase events using the same transaction_id as the original purchase. This allows GA4 to track upsells as additional revenue on the same order.
User Events
| SDK Event | GA4 Event |
|---|---|
dl_sign_up | sign_up |
dl_login | login |
Features
1. Duplicate Prevention
The bridge tracks processed events and prevents duplicates:
// Events are deduplicated using:
// - Event name
// - Sequence number
// - Item ID
// - Order ID2. Upsell Tracking
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.
3. Ecommerce Object Clearing
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: { ... }
});4. Memory Management
Automatically cleans up old data to prevent memory leaks:
- Keeps last 500 processed events
- Keeps last 50 transaction IDs
- Runs cleanup every 60 seconds
5. Debug Mode
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-12345Bridge API
The bridge exposes debugging utilities on window.GA4Bridge:
Get Processed Event Count
const count = window.GA4Bridge.getProcessedCount();
console.log(`Processed ${count} events`);View Event Mapping
const mapping = window.GA4Bridge.getEventMap();
console.log(mapping);
// {
// 'dl_add_to_cart': 'add_to_cart',
// 'dl_purchase': 'purchase',
// ...
// }View Transaction Map
const transactions = window.GA4Bridge.getTransactionMap();
console.log(transactions);
// {
// '123': { transaction_id: 'ORD-12345', order_number: '12345' },
// ...
// }Check Bridge Status
const isActive = window.GA4Bridge.isActive();
console.log(`Bridge active: ${isActive}`);Example Event Flow
Cart Event
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
}]
}
});Purchase Event
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: [...]
}
});Upsell Event
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'
}]
}
});Troubleshooting
Events Not Appearing in GA4
-
Check GA4 is loaded
console.log(typeof gtag); // Should be 'function' -
Check bridge is active
console.log(window.GA4Bridge.isActive()); // Should be true -
Check events are being converted
- Open browser console
- Add
?debug=trueto URL - Look for
[GA4 Bridge]log messages
-
Verify NextDataLayer exists
console.log(window.NextDataLayer); // Should be an array
Duplicate Events
The bridge automatically prevents duplicates, but if you see duplicates:
- Make sure you're only loading the bridge script once
- Check that you're not also pushing events to
window.dataLayermanually - Verify the bridge is loaded after the SDK
Upsells Not Tracking
If upsells aren't showing up as purchases:
-
Check transaction map
console.log(window.GA4Bridge.getTransactionMap()); -
Verify purchase event fired first
- The initial purchase must fire before upsells
- Transaction ID is stored from the purchase event
-
Check order_id is present
- Upsell events need
order_idto look up transaction_id
- Upsell events need
Missing Transaction IDs
If upsells show undefined transaction_id:
- The initial
dl_purchaseevent must includeecommerce.transaction_id - The upsell event must include
order_idmatching the purchase - Check the transaction map:
window.GA4Bridge.getTransactionMap()
Performance
The bridge is lightweight and efficient:
- ~5KB minified
- Processes events in <1ms
- Memory-safe with automatic cleanup
- No external dependencies
Comparison: Bridge vs GTM
| Feature | GA4 Bridge | Google Tag Manager |
|---|---|---|
| Setup complexity | Simple (1 script) | Complex (container setup) |
| Event conversion | Automatic | Manual triggers/tags |
| Upsell handling | Built-in | Manual configuration |
| Deduplication | Automatic | Must configure |
| Additional tracking | Limited | Unlimited |
| Tag management | None | Full tag management |
| Best for | Direct GA4 | Multiple platforms |
When NOT to Use This
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.
Build Your Own Transformer
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
Related Documentation
- Event Transformers - General pattern for any platform
- Google Tag Manager Setup - Alternative using GTM
- Analytics Overview - Main analytics documentation
- Event Reference - All available events
- Configuration - SDK configuration options