User-ID Implementation
GA4’s default Client ID is cookie-based and device-specific — a user who browses on their phone and converts on their laptop appears as two separate users. The User-ID feature lets you associate a consistent, server-generated identifier with each authenticated user, enabling cross-device attribution and accurate user counts.
How User-ID works in GA4
Section titled “How User-ID works in GA4”- A user logs in to your site
- Your server generates (or retrieves) an opaque user identifier — a database primary key, a UUID, or a hashed internal ID
- You push this identifier to the dataLayer
- GTM reads it and sets
user_idon the GA4 Configuration tag - GA4 associates all subsequent events in that session with the user identifier
- When the same user logs in on another device, GA4 merges their activity into a unified user record
Step 1 — Generate the user identifier
Section titled “Step 1 — Generate the user identifier”Use your database primary key or a UUID — never an email address:
# Backend example — include in your page render contextimport uuid
def get_or_create_analytics_id(user): if not user.analytics_id: user.analytics_id = str(uuid.uuid4()) user.save() return user.analytics_id// Node.js / Express exampleapp.get('/dashboard', authenticate, (req, res) => { res.render('dashboard', { analyticsUserId: req.user.analyticsId // Opaque ID, not email });});Step 2 — Push to the dataLayer
Section titled “Step 2 — Push to the dataLayer”Push the user identifier on any page where the user is authenticated. Typically this means all pages after login:
Push immediately on page load for authenticated users. Place before GTM snippet if possible.
<!-- Server-rendered template — replace SERVER_VAR with your template syntax --><script> window.dataLayer = window.dataLayer || [];
{% if user.is_authenticated %} window.dataLayer.push({ event: 'user_identified', user_id: '{{ user.analytics_id }}', user_login_status: 'logged_in', // Optional: non-PII user segments user_type: '{{ user.plan_type }}', // 'free', 'pro', 'enterprise' user_tenure_days: {{ user.days_since_signup }} }); {% else %} window.dataLayer.push({ user_login_status: 'logged_out' }); {% endif %}</script><!-- GTM snippet goes here -->For React/Next.js, set this in a server component or getServerSideProps:
// Next.js pages/_app.js or app/layout.jsexport default function Layout({ children, session }) { useEffect(() => { if (session?.user) { window.dataLayer = window.dataLayer || []; window.dataLayer.push({ event: 'user_identified', user_id: session.user.analyticsId, user_login_status: 'logged_in', user_type: session.user.planType }); } }, [session]);
return <>{children}</>;}Step 3 — Configure GTM
Section titled “Step 3 — Configure GTM”-
Create Data Layer Variables
DLV - user_id→ Data Layer Variable name:user_idDLV - user_type→user_typeDLV - user_login_status→user_login_status
-
Add
user_idto your Google Tag / GA4 ConfigurationOpen your Google Tag in GTM → Configuration settings → Fields to set:
- Field:
user_id - Value:
{{DLV - user_id}}
This sets
user_idon every GA4 hit when the variable is populated. - Field:
-
Create a Custom Event Trigger
- Trigger type: Custom Event
- Event name:
user_identified
-
Create a GA4 Event Tag for user properties
- Tag type: Google Analytics: GA4 Event
- Event name:
login - User Properties:
user_type→{{DLV - user_type}}
- Trigger: the Custom Event trigger
-
Register user properties in GA4
Go to GA4 → Admin → Custom Definitions → User Properties:
user_type: User scopeuser_login_status: User scope
-
Enable User-ID reporting in GA4
Go to GA4 → Admin → Reporting Identity and set it to By User ID and Device (or Blended). This enables GA4 to merge sessions across devices when User-ID data is available.
GA4 - login (with user properties)
- Type
- Google Analytics: GA4 Event
- Trigger
- Custom Event - user_identified
- Variables
-
DLV - user_idDLV - user_type
Handling logout
Section titled “Handling logout”When a user logs out, clear the user_id from the dataLayer to prevent subsequent events from being attributed to the user:
function handleLogout() { window.dataLayer = window.dataLayer || []; window.dataLayer.push({ event: 'logout', user_id: undefined, // Clear the user_id user_type: undefined, user_login_status: 'logged_out' }); // Proceed with logout logic}Test it
Section titled “Test it”- Log in to your site and open GTM Preview
- Verify the
user_identifiedevent fires on page load - Check the Variables tab —
DLV - user_idshould contain your opaque user identifier (not an email) - In GA4 DebugView, verify events show
user_idin the user properties section - Log in on a different device/browser with the same account — verify GA4 merges the sessions
Common gotchas
Section titled “Common gotchas”User-ID is an email address. This is the most common mistake. Email addresses are PII and cannot be sent to GA4 without proper processing. Use a database ID or a UUID. If you only have an email, hash it first: sha256(email) is acceptable but opaque IDs are better.
user_id is not being set before the first hit. If you push user_id after the GA4 Configuration tag has already fired on page load, the first page view will not have user_id set. Push to the dataLayer before the GTM snippet loads for authenticated pages.
Consent gates user identification. If the user has not consented to analytics storage, you should not set user_id. Guard the dataLayer push behind your consent state check.