Cross-Domain Tracking
Cross-domain tracking enables you to track a single user session across multiple domains. Without it, a user visiting shop.example.com after www.example.com would appear as two separate users with two separate sessions.
Why Cross-Domain Tracking Matters
When a user moves between domains, their cookies don't travel with them. This causes:
- Session fragmentation: One user journey becomes multiple sessions
- Inflated user counts: Same user counted multiple times
- Broken attribution: Original traffic source lost on domain change
- Inaccurate conversion data: Conversions attributed to "referral" from your own domain
Common Multi-Domain Scenarios
| Scenario | Domains Involved |
|---|---|
| Main site + checkout | example.com → checkout.example.com |
| Marketing + product | marketing.example.com → app.example.com |
| Country-specific sites | example.com → example.co.uk |
| Third-party checkout | store.example.com → shopify.example.com |
| Landing pages + main site | promo.example.com → www.example.com |
How Cross-Domain Tracking Works
Cross-domain tracking passes user identification data via URL parameters when navigating between domains. The receiving domain reads these parameters and "adopts" the existing user session instead of creating a new one.
User clicks link on domain-a.com
↓
Link URL decorated with _gl parameter
https://domain-b.com?_gl=1*abc123*_ga*MTIzNDU2Nzg5*...
↓
domain-b.com reads _gl parameter
↓
Same client_id used, session continues
The _gl parameter contains encoded client ID and session data that allows the receiving domain to identify the user.
GA4 Implementation
Basic Configuration
In your GA4 configuration, add all domains that should share sessions:
// gtag.js implementation
gtag('config', 'G-XXXXXXXXXX', {
'linker': {
'domains': ['example.com', 'checkout.example.com', 'app.example.com']
}
});
This automatically decorates all links pointing to the listed domains with the _gl parameter.
Full Implementation Example
<!-- In <head> section of ALL domains -->
<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', {
// List ALL domains that should share sessions
'linker': {
'domains': [
'example.com',
'www.example.com',
'checkout.example.com',
'app.example.com'
],
// Accept incoming linker parameters
'accept_incoming': true,
// Decorate forms (for POST submissions)
'decorate_forms': true
}
});
</script>
Important Settings
| Setting | Purpose | Default |
|---|---|---|
domains |
List of domains to link | Required |
accept_incoming |
Read incoming _gl parameters | true |
decorate_forms |
Add linker to form actions | false |
url_position |
Parameter position (query or fragment) | query |
Including Subdomains
For subdomains, you have two options:
// Option 1: List each subdomain explicitly
'linker': {
'domains': ['example.com', 'shop.example.com', 'blog.example.com']
}
// Option 2: Use automatic subdomain matching (GA4 does this by default)
// Same root domain subdomains share cookies automatically
// Cross-domain is only needed for DIFFERENT root domains
Note: Subdomains of the same root domain (e.g., www.example.com and shop.example.com) typically share cookies automatically in GA4. Cross-domain tracking is primarily needed for completely different domains.
GTM Implementation
Step 1: Configure GA4 Configuration Tag
- Open your GA4 Configuration tag in GTM
- Scroll to "Fields to Set"
- Add these fields:
| Field Name | Value |
|---|---|
linker |
(see configuration object below) |
Or use the dedicated "Cross Domain Measurement" section in newer GTM versions.
Step 2: Cross-Domain Configuration
In the GA4 Configuration tag:
- Click "Configure tag settings"
- Find "Configure your domains"
- Add each domain (one per line):
example.comcheckout.example.comapp.example.com
Step 3: Custom JavaScript Variable (Advanced)
For dynamic domain configuration:
// GTM Custom JavaScript Variable: "cjs - Linker Domains"
function() {
return {
'domains': [
'example.com',
'checkout.example.com',
'app.example.com'
],
'accept_incoming': true,
'decorate_forms': true
};
}
Then reference {{cjs - Linker Domains}} in your GA4 Configuration tag's linker field.
Handling Form Submissions
Forms that POST to another domain need special handling since the URL isn't visible:
gtag('config', 'G-XXXXXXXXXX', {
'linker': {
'domains': ['example.com', 'checkout.example.com'],
'decorate_forms': true // Critical for forms
}
});
Manual Form Decoration
For dynamically loaded forms or custom implementations:
// Manually decorate a form's action URL
document.querySelector('form.checkout-form').addEventListener('submit', function(e) {
// gtag automatically decorates if decorate_forms is true
// For manual control:
var form = e.target;
var action = form.getAttribute('action');
// Use GA's linker to decorate the URL
gtag('get', 'G-XXXXXXXXXX', 'client_id', function(clientId) {
// Append client_id to form action
var separator = action.includes('?') ? '&' : '?';
form.setAttribute('action', action + separator + '_gl_cid=' + clientId);
});
});
JavaScript Navigation
For JavaScript-triggered navigation (SPAs, custom links):
// Custom navigation function with linker support
function navigateToDomain(url) {
// Let gtag decorate the URL first
gtag('get', 'G-XXXXXXXXXX', 'linker_param', function(linkerParam) {
if (linkerParam) {
var separator = url.includes('?') ? '&' : '?';
window.location.href = url + separator + linkerParam;
} else {
window.location.href = url;
}
});
}
// Usage
document.querySelector('.external-checkout').addEventListener('click', function(e) {
e.preventDefault();
navigateToDomain('https://checkout.example.com/cart');
});
Debugging Cross-Domain Tracking
Verify Link Decoration
- Open browser DevTools
- Right-click a cross-domain link
- Copy link address
- Check for
_glparameter:
https://checkout.example.com/cart?_gl=1*1abc2de*_ga*MTIzNDU2Nzg...
If the _gl parameter is missing, check your domain configuration.
Console Debugging
// Check if linker is configured
gtag('get', 'G-XXXXXXXXXX', 'linker_param', function(param) {
console.log('Linker parameter:', param);
});
// Check client ID consistency
gtag('get', 'G-XXXXXXXXXX', 'client_id', function(clientId) {
console.log('Client ID:', clientId);
});
// Monitor all link clicks
document.addEventListener('click', function(e) {
if (e.target.tagName === 'A') {
console.log('Link href:', e.target.href);
console.log('Contains _gl:', e.target.href.includes('_gl='));
}
}, true);
Verify Session Continuity
On both domains, check that client_id matches:
// Run this on domain-a.com before clicking link
gtag('get', 'G-XXXXXXXXXX', 'client_id', function(id) {
console.log('Domain A client_id:', id);
});
// Run this on domain-b.com after arriving
gtag('get', 'G-XXXXXXXXXX', 'client_id', function(id) {
console.log('Domain B client_id:', id);
});
// IDs should match if cross-domain is working
GA4 DebugView Verification
- Enable debug mode on both domains
- Navigate from domain A to domain B
- In GA4 DebugView, events should show:
- Same client_id (user identifier)
- Continuous session_id
- No new
session_startevent on domain B
Common Problems and Solutions
Problem: Links Not Being Decorated
Symptoms: _gl parameter missing from cross-domain links
Causes and fixes:
// 1. Domain not in list (most common)
// WRONG:
'domains': ['example.com'] // Missing checkout domain
// CORRECT:
'domains': ['example.com', 'checkout.example.com']
// 2. Domain format mismatch
// WRONG:
'domains': ['www.example.com'] // Won't match example.com
// CORRECT:
'domains': ['example.com', 'www.example.com'] // Include both
// 3. JavaScript links not triggering
// WRONG: Direct assignment
window.location = 'https://other-domain.com';
// CORRECT: Use gtag to get decorated URL
gtag('get', 'G-XXXXXXXXXX', 'linker_param', function(param) {
window.location = 'https://other-domain.com?' + param;
});
Problem: Self-Referral Traffic
Symptoms: Your own domain appearing as referral source
Causes and fixes:
Missing domain in referral exclusion list:
- GA4 Admin → Data Streams → Configure tag settings
- List internal domains under "Referral exclusion"
Subdomain cookie scope issues:
// Ensure cookie domain is set to root domain gtag('config', 'G-XXXXXXXXXX', { 'cookie_domain': '.example.com' // Note the leading dot });
Problem: Session Breaks on Domain Switch
Symptoms: New session starts on second domain, different client_id
Causes and fixes:
// 1. Check accept_incoming is enabled (default true)
gtag('config', 'G-XXXXXXXXXX', {
'linker': {
'domains': ['domain-a.com', 'domain-b.com'],
'accept_incoming': true // Must be true on receiving domain
}
});
// 2. Verify _gl parameter isn't stripped
// Check server/CDN config isn't removing query parameters
// Check URL rewrite rules in .htaccess or nginx config
// 3. Check for consent mode blocking
// If consent denied, linker might not work
gtag('consent', 'update', {
'analytics_storage': 'granted' // Required for cross-domain
});
Problem: _gl Parameter Causes 404 or Errors
Symptoms: Pages break when _gl parameter is present
Fixes:
// Option 1: Use fragment instead of query parameter
gtag('config', 'G-XXXXXXXXXX', {
'linker': {
'domains': ['example.com'],
'url_position': 'fragment' // Uses # instead of ?
}
});
// Option 2: Server-side - ignore the parameter
// In nginx:
// location / {
// if ($args ~* "_gl=") {
// rewrite ^(.*)$ $1? redirect; // Strip query
// }
// }
Problem: Iframe Cross-Domain
Symptoms: Parent and iframe sessions not linked
Iframes on different domains cannot be linked via standard cross-domain tracking due to security restrictions. Options:
// Option 1: Pass client_id via postMessage
// Parent page
gtag('get', 'G-XXXXXXXXXX', 'client_id', function(clientId) {
document.querySelector('iframe').contentWindow.postMessage({
type: 'ga_client_id',
clientId: clientId
}, 'https://iframe-domain.com');
});
// Iframe page
window.addEventListener('message', function(event) {
if (event.origin === 'https://parent-domain.com' &&
event.data.type === 'ga_client_id') {
gtag('config', 'G-XXXXXXXXXX', {
'client_id': event.data.clientId
});
}
});
// Option 2: Include client_id in iframe src
var iframe = document.querySelector('iframe');
gtag('get', 'G-XXXXXXXXXX', 'client_id', function(clientId) {
iframe.src = 'https://iframe-domain.com/page?cid=' + clientId;
});
Testing Checklist
Before launching cross-domain tracking:
- All domains listed in linker configuration
- Configuration deployed to ALL domains
- Links are decorated with
_glparameter - Forms are decorated (
decorate_forms: true) - JavaScript navigation handles linker
- Client_id matches across domains (test in console)
- No new session_start on domain switch (DebugView)
- No self-referrals in traffic reports
- Referral exclusion list includes all domains
- Consent mode doesn't block linker
- Server doesn't strip
_glparameter - Old links/bookmarks with
_glwork
Privacy Considerations
Cross-domain tracking raises privacy implications:
- User Disclosure: Inform users that tracking spans multiple sites
- Consent Requirements: May require explicit consent under GDPR/CCPA
- Privacy Policy: Update to reflect cross-domain data sharing
- Data Minimization: Only implement if genuinely needed for business purposes
// Respect user consent preferences
if (userHasConsented) {
gtag('config', 'G-XXXXXXXXXX', {
'linker': {
'domains': ['example.com', 'checkout.example.com']
}
});
} else {
gtag('config', 'G-XXXXXXXXXX', {
// No linker - each domain tracked separately
});
}
Related Resources
- Debugging GA4 - General GA4 troubleshooting
- Debugging GTM - Tag Manager issues
- Data Layers - Passing data to analytics
- Session Attribution Issues - Attribution problems