I’m trying to figure out how to send tracking data to GA4 from my backend instead of the browser. Previously I used a package called analytics-universal to send events from my Express.js application, which worked perfectly fine.
Now that GA4 is the standard version, I want to migrate but I can’t find any guides for server-side implementation. All the examples I see are for frontend JavaScript and they don’t work when I try them in Node.js.
Basically I need the backend equivalent of this frontend code:
GA4 server-side tracking works, but Google made it way more complicated than Universal Analytics.
You’ll use the Measurement Protocol for GA4. The endpoint is https://www.google-analytics.com/mp/collect and you need your measurement ID and API secret from your GA4 property settings.
Managing authentication, payload formatting, and error handling gets messy fast. You also need proper client ID generation and session management.
I hit this same problem last year and automated the whole thing instead of writing custom code. Set up a workflow that listens for events from your Express app and handles all the GA4 formatting automatically. Way cleaner than maintaining API calls in your codebase.
The workflow handles retries, validates data format, and batches multiple events. Takes about 10 minutes to set up and you never think about GA4’s quirky API again.
theres an npm package called @google-analytics/data, but its mainly for reading data, not sending events. for server-side tracking, you’ll need Measurement Protocol v4 like the other person mentioned. just make sure you generate proper client_ids - i use the uuid package for that. also set the user-agent header or google might reject your requests.
Had the same Universal Analytics migration headache. Ended up using the gtag npm package - works great for server-side GA4. Documentation is lacking, but it simplifies the Measurement Protocol process. Just install gtag, initialize it with your measurement ID, and send events as you would on the frontend. The main difference is setting up the transport since there’s no browser. Make sure to handle client_id properly; I store them in sessions or create deterministic ones based on user IDs. Also, be cautious with event validation, as GA4 is more stringent with parameter names and data types than Universal Analytics. I had events that appeared to send correctly but didn’t show up due to incorrect parameter structures. Utilize the debug endpoint during development to save time.
Had this same migration nightmare 6 months ago. Everyone’s right about using Measurement Protocol, but here’s what’ll save you debugging time. Make a simple wrapper function with proper error handling - GA4 fails silently on bad requests: javascript const sendEvent = async (eventName, userId, customParams = {}) => { const payload = { client_id: userId || require('crypto').randomUUID(), user_id: userId, events: [{ name: eventName, parameters: { ...customParams, timestamp_micros: Date.now() * 1000 } }] }; try { const response = await fetch(`https://www.google-analytics.com/mp/collect?measurement_id=${GA4_ID}&api_secret=${API_SECRET}`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) }); if (!response.ok) throw new Error(`GA4 failed: ${response.status}`); } catch (error) { console.error('GA4 tracking failed:', error); } }; Critical thing everyone missed: set up custom dimensions in your GA4 property FIRST if you’re sending custom parameters. They won’t show in reports otherwise, even though events register fine.