A lightweight JavaScript library that intercepts dataLayer events and forwards them to a server-side GTM container. This can be used when moving complex applications to server side tagging while moving away from web tagging containers.
DataLayer Relay is a client-side script that sends all datalayer events to server side tagging. It bundles custom variables to one json object so it can be used for server side tags.
This repository provides:
- Template-based relay script (
src/datalayer-relay.js) with environment variable substitution. This can be used as base for the integration. - Complete Docker stack for local development and testing.
- Test environment to verify your GTM configuration
# Copy the example environment file
cp .env.example .env
# Edit with your actual values
nano .envSet the following in .env:
# Get this from GTM Admin > Container Settings
CONTAINER_CONFIG=your_container_config_here
# Your GA4 Measurement ID (e.g., G-ABC123XYZ)
GA4_PROPERTY=G-XXXXXXXXXXSSL certificates are automatically generated. Just run:
docker-compose up -dThis starts:
- GTM Preview Server (for debugging)
- GTM Live Server (for production simulation)
- Nginx HTTPS Proxy (ports 8888, 8889)
- Template Server (port 3000) - serves your test site with dynamic config
# Check all containers are healthy
docker-compose ps
# Verify GTM containers
curl -k https://localhost:8888/healthy # Live server
curl -k https://localhost:8889/healthy # Preview server
# Open test site
open http://localhost:3000Add https://localhost:8888 to your server side container sites. Now you can preview and debug a server side container without server setup.
That's it! Click the test buttons and watch events flow through your server-side GTM container.
The system consists of 5 Docker services:
βββββββββββββββββββββββββββββββββββββββββββββββ
β Browser β
β ββ> http://localhost:3000 (Test Site) β
β ββ> https://localhost:8888 (GTM Live) β
β ββ> https://localhost:8889 (GTM Preview) β
βββββββββββββββββββββββββββββββββββββββββββββββ
β
βββββββββββββββββββββββββββββββββββββββββββββββ
β ssl-init (one-time) β
β ββ> Generates SSL certificates β
βββββββββββββββββββββββββββββββββββββββββββββββ
β
βββββββββββββββββββββββββββββββββββββββββββββββ
β template-server (Node.js) β
β ββ> Serves test site (port 3000) β
β ββ> Injects GA4_PROPERTY & URL at runtime β
βββββββββββββββββββββββββββββββββββββββββββββββ
β
βββββββββββββββββββββββββββββββββββββββββββββββ
β nginx (HTTPS Proxy) β
β ββ> Port 8888 β gtm-live β
β ββ> Port 8889 β gtm-preview β
βββββββββββββββββββββββββββββββββββββββββββββββ
β
ββββββββββββββββββββ βββββββββββββββββββββββ
β gtm-live ββββββ gtm-preview β
β (Production) β β (Debug Mode) β
ββββββββββββββββββββ βββββββββββββββββββββββ
| Service | Container | Ports | Purpose |
|---|---|---|---|
| ssl-init | ssl-init | - | Generates SSL certificates (runs once) |
| gtm-preview | gtm-preview | Internal:8080 | GTM Preview Server |
| gtm-live | gtm-live | Internal:8080 | GTM Live Server |
| nginx | gtm-nginx | 8888, 8889 | HTTPS Proxy for GTM |
| template-server | template-server | 3000 | Test site + dynamic config |
The template server (server.js) dynamically injects environment variables into JavaScript and HTML files:
Before (in src/datalayer-relay.js):
var MEASUREMENT_ID = '{{GA4_PROPERTY}}';
var SERVER_CONTAINER_URL = '{{SERVER_CONTAINER_URL}}';After (served to browser):
var MEASUREMENT_ID = 'G-ABC123XYZ';
var SERVER_CONTAINER_URL = 'https://localhost:8888';src/datalayer-relay.js- Relay script with placeholderstest-site/index.html- Test page (if needed)
{{GA4_PROPERTY}}- Your GA4 Measurement ID{{SERVER_CONTAINER_URL}}- Your GTM server container URL
-
CONTAINER_CONFIG- Base64-encoded GTM container configuration- Get from: GTM Admin > Container Settings > Container Config
- Format: Base64 string
-
GA4_PROPERTY- Your GA4 Measurement ID- Format:
G-XXXXXXXXXX - Get from: Google Analytics > Admin > Data Streams
- Format:
-
SERVER_CONTAINER_URL- GTM server container URL- Default:
https://localhost:8888 - Change for production deployment
- Default:
-
PORT- Template server port- Default:
3000
- Default:
# GTM Configuration
CONTAINER_CONFIG=aWQ9R1RNLVdSOUo0NTROJmVudj0xJmF1dGg9bnRMejlYRHhVU1RBd1VaOHdSb3N2dw==
# GA4 Property
GA4_PROPERTY=G-2JEHB71L3G
# Server URL (optional)
SERVER_CONTAINER_URL=https://localhost:8888The relay script supports persistent fields - parameters that, once set, are automatically included in all subsequent events until they're updated or the page is reloaded.
- You define which fields should persist by editing the
PERSISTENT_FIELDSarray insrc/datalayer-relay.js - When an event sets one of these fields, the value is stored in memory
- All subsequent events automatically include the last known value of each persistent field
- If a new event provides a different value for a persistent field, it updates the stored value
// In src/datalayer-relay.js, configure persistent fields:
var PERSISTENT_FIELDS = ['user_type', 'subscription_tier', 'session_id'];// Event 1: User logs in
dataLayer.push({
event: 'login',
user_type: 'premium',
subscription_tier: 'gold'
});
// Event 2: Page view (no user fields specified)
dataLayer.push({
event: 'page_view',
page_title: 'Dashboard'
});
// β Automatically includes user_type='premium' and subscription_tier='gold'
// Event 3: Subscription upgraded
dataLayer.push({
event: 'subscription_change',
subscription_tier: 'platinum' // Updates the persistent value
});
// Event 4: Purchase
dataLayer.push({
event: 'purchase',
transaction_id: 'T123',
value: 99.99
});
// β Automatically includes user_type='premium' and subscription_tier='platinum'Edit the PERSISTENT_FIELDS array in src/datalayer-relay.js (around line 65):
// Default (no persistence)
var PERSISTENT_FIELDS = [];
// Example with persistence enabled
var PERSISTENT_FIELDS = ['user_type', 'subscription_tier', 'session_id', 'user_id'];- Session Scope: Persistent values are stored in memory and reset on page reload
- Precedence: If an event explicitly sets a persistent field, that value takes precedence and updates the stored value
- No Storage: Values are NOT saved to localStorage or cookies - they exist only during the current page session
- Debug Mode: When
DEBUG = true, you'll see[Persistence] Updated field_name = valuelogs in the console
- Start the stack:
docker-compose up -d - Open test site: http://localhost:3000
- Open DevTools: Press F12 or right-click > Inspect
- Fire events: Click any test button
- Verify:
- Check browser Console for relay logs
- Check Network tab for requests to
localhost:8888 - View Event Log on the page
- Use GTM Debug Mode to see events in GTM
The test site provides 6 event scenarios:
- Page View - Basic page view tracking
- Purchase - E-commerce purchase with items
- Form Submit - Custom form submission event
- Custom Interaction - Mixed standard + custom parameters
- Add to Cart - E-commerce add to cart
- User Login - Authentication event
# Check container status
docker-compose ps
# View logs for specific service
docker-compose logs gtm-live
docker-compose logs gtm-preview
docker-compose logs template-server
docker-compose logs nginx
docker-compose logs ssl-init
# Check all logs
docker-compose logs -fSSL certificates are automatically generated. If there are issues:
# Manually regenerate certificates
./generate-ssl.sh
# Check if certificates exist
ls -la ssl/
# Remove and regenerate
rm -rf ssl/
docker-compose up -d# Check if environment variables are loaded
docker exec template-server env | grep GA4_PROPERTY
docker exec template-server env | grep SERVER_CONTAINER_URL
# Ensure you're accessing via http://localhost:3000 (not file://)
# Browser must load through the template server for substitution to work# Verify CONTAINER_CONFIG is set
docker exec gtm-live env | grep CONTAINER_CONFIG
docker exec gtm-preview env | grep CONTAINER_CONFIG
# If empty, check your .env file
cat .env
# Restart containers after .env changes
docker-compose restart# Find what's using port 3000
lsof -i :3000
# Kill the process (replace PID)
kill -9 <PID>
# Or change the port in .env
echo "PORT=3001" >> .env
docker-compose up -dSelf-signed certificates will show browser warnings. This is normal for local development.
Chrome: Click "Advanced" β "Proceed to localhost (unsafe)" Firefox: Click "Advanced" β "Accept the Risk and Continue" Safari: Click "Show Details" β "visit this website"
# Stop and remove all containers
docker-compose down
# Remove everything including images
docker-compose down --rmi all --volumes
# Remove SSL certificates
rm -rf ssl/
# Start fresh
docker-compose up -d --buildMIT License - feel free to use this in your projects!
