Concepts
GTM vs. codebase tracking
Two ways to wire up analytics. They're not mutually exclusive, but the architectural choice matters.
There are two ways to wire up analytics: install the SDK directly in your codebase, or route events through Google Tag Manager (GTM). They're not mutually exclusive — most production setups use both for different purposes — but the architectural choice matters.
The problem GTM solves
Most SaaS products eventually need to send the same events to multiple destinations. You're tracking purchases in Amplitude for product analytics, in Facebook Pixel for ad attribution, in Google Ads for conversion tracking, in HubSpot for CRM. One business event, four destinations.
If you implement this directly in your codebase, your purchase handler ends up like this:
const handlePurchaseComplete = (orderData) => {
// Amplitude
amplitude.track('Purchase completed', {
revenue: orderData.amount,
orderId: orderData.id,
});
// Facebook Pixel
fbq('track', 'Purchase', {
value: orderData.amount,
currency: orderData.currency,
});
// Google Ads
gtag('event', 'purchase', {
value: orderData.amount,
currency: orderData.currency,
transaction_id: orderData.id,
});
// HubSpot, and so on...
};Four implementations of the same event. When marketing wants to add a fifth destination, an engineer has to write more code. When they want to add a property to one of the destinations, an engineer has to make the change. Every tracking adjustment becomes a code change, a code review, and a deploy.
How GTM works
GTM introduces an intermediate layer. You install GTM's script on your site, which creates a dataLayer object. Your codebase pushes events to the dataLayer instead of directly to destinations:
const handlePurchaseComplete = (orderData) => {
dataLayer.push({
event: 'Purchase completed',
revenue: orderData.amount,
orderId: orderData.id,
currency: orderData.currency,
});
};That's the whole code change. From there, marketing (or whoever owns analytics) configures GTM through its UI to listen for this event and forward it to Amplitude, Facebook, Google Ads, HubSpot, or any other destination. They can adjust properties, add filters, route to new destinations — all without code changes.
When to use GTM
Use GTM when:
- You need to send events to multiple destinations (ad platforms, CRMs, email tools, plus your analytics tool)
- You want non-engineers to manage tracking without going through engineering
- You expect the destination list to change over time (new ad platforms, swapping CRMs)
Use codebase-only when:
- You're sending events to one destination only (just Amplitude, or just PostHog)
- You want complete control in version control and a clear audit trail
- Your team is engineering-led and there's no demand from marketing to manage tracking independently
For SaaS products beyond the earliest stage, GTM is the standard. It's what most teams converge on once they're integrating with even one marketing tool beyond their core analytics platform.
The catch with GTM and server-side events
GTM lives in the browser. It can route client-side events to multiple destinations brilliantly. It can't help with server-side events.
Your revenue events (which need to be server-side for accuracy — see Client-side vs. server-side tracking) bypass GTM entirely. They go directly from your server to each destination. If you want a Stripe-triggered conversion event to reach both Amplitude and Facebook Ads, you're sending it twice from your server.
GTM has a feature called "server-side GTM" that addresses this — you run a GTM container on your own server that handles routing. It's an additional setup with its own infrastructure cost. For most teams it's overkill; you can just call each destination directly from your webhook handler.
The hybrid that's typical
Most production setups look like this:
- Client-side events (page views, UI interactions, feature events) flow through GTM. Marketing manages the destination routing.
- Server-side events (revenue, subscription state changes, anything requiring 100% accuracy) go directly from your backend to each destination, called explicitly in your webhook handlers.
This gives you marketing flexibility for the events that benefit from it, and reliability for the events that need it. The downside is two parallel tracking pipelines to maintain, but in practice the server-side pipeline is small (just your webhook handlers) and the client-side pipeline is what most of the volume flows through anyway.
A note on this guide's implementation chapters
The implementation chapters in this guide show direct codebase calls. If you're using GTM, the dataLayer pushes substitute for the SDK calls; the triggers (when to fire what event with what properties) are identical.
The reasoning behind those triggers — what to track, when, with what context — is what the concepts section covers. That doesn't change based on whether the calls go through GTM or not.