By Joseph Darnell · August 10, 2026 · 13 min read ·

How we stopped counting ourselves: an internal-traffic cookie built in GTM

Every QA pass, every "let me just check that," every refresh from the team was quietly inflating our own GA4 numbers. IP-based exclusion doesn't survive a dynamic IP. Here's the first-party cookie we built in GTM instead — and the one link everyone on the team has to click, once.

GA4Google Tag ManagerAnalyticsCookiesAutomationPractitioner Field Report

IP-based GA4 exclusion breaks the moment anyone's IP changes, which for most home connections is a matter of weeks. We replaced it with a first-party cookie built entirely in Google Tag Manager: visit one link once, and that browser stops being counted for two years, no matter how many times your ISP reassigns your address. The mechanism is four pieces inside GTM: a cookie variable, a page view trigger, a GA4 configuration exception, and a Custom HTML tag that sets the cookie. No code deployed to the site itself, no server-side changes, nothing for a client to maintain after we hand off a dashboard.

Every analytics dashboard has the same blind spot: the people building the site are also its most frequent visitors. Every deploy check, every "does this look right on mobile," every link we click before sending it to a client — GA4 counts all of it as real traffic, sitting quietly inside session counts, bounce rate, and every report we hand a client. It's the same event-tracking layer we describe in how we build the Automaton stack — useful data, undermined by noise from the builders themselves.

Why IP exclusion is the wrong fix

The standard fix is IP-based exclusion, and it's the first thing GA4's own setup docs point you to: go to Admin, Data Streams, Configure Tag Settings, Show More, Define Internal Traffic, paste in your team's IP addresses. It works exactly as described — for about as long as your IP address stays the same.

That's the problem. Most residential ISPs assign dynamic IPs that rotate on their own schedule, sometimes every few days, sometimes every time a router restarts. Mobile connections rotate constantly by design. Anyone working from a coffee shop, a co-working space, or a second location shows up with an address that was never on the list to begin with. We were re-adding IPs every few weeks and still catching our own visits in the data — not because the feature was broken, but because it was built for a kind of office network that increasingly doesn't exist: one fixed building, one static IP, one team that never works remotely.

There's a second failure mode that's easy to miss: GA4's internal traffic rule only filters events after they're captured. The pageview still fires, still counts against your event quota, still shows up in real-time reports and in any tool reading raw BigQuery exports before the internal-traffic filter gets applied downstream. IP exclusion hides the noise in the interface. It doesn't stop the noise from happening.

The fix that actually holds: a first-party cookie set once per browser, checked on every page view — not an IP. A cookie doesn't change when your router reassigns an address. It travels with the browser, not the connection.

The mechanism

Here's the mechanism, built entirely in Google Tag Manager, no code on the site itself:

  1. A 1st Party Cookie variable in GTM reads a cookie called automaton_internal. This is a native GTM variable type — you're not writing JavaScript to read it, just pointing GTM at the cookie name.
  2. A Page View trigger checks that variable on every page load, firing only when the cookie's value equals true.
  3. A GA4 configuration tag exception uses that same trigger, but inverted: it tells GA4 not to fire when the cookie is present. Internal visits simply never get sent to the property in the first place, instead of being sent and filtered out later. This is the piece that solves the quota and real-time-report problem that IP exclusion can't touch.
  4. A Custom HTML tag, fired once from a specific URL trigger, sets the cookie itself with a two-year expiration.

Setting it up, step by step

If you're building this for your own site, the order matters — the exception in step 3 depends on the trigger from step 2 already existing.

  1. Create the cookie variable first. In GTM, go to Variables → New → 1st Party Cookie. Name it something identifiable (we used Cookie - Internal Traffic) and point it at the cookie name you'll set later — automaton_internal, or whatever you choose. This variable doesn't do anything on its own yet; it just gives the rest of the setup something to check.
  2. Build the trigger that reads it. Triggers → New → Page View, firing on "Some Page Views," with the condition set to the cookie variable from step 1 equals true. This is the trigger your GA4 exception will use.
  3. Add the exception to your existing GA4 configuration tag. Open the GA4 config tag you already have running the rest of your tracking. Under Triggering, expand "Exceptions" and add the trigger from step 2. This is the entire fix — one exception, added to a tag you already have.
  4. Build the tag that sets the cookie. Tags → New → Custom HTML. This tag writes the cookie with a name, a value of true, and an expiration roughly two years out. Set its trigger to a specific URL condition — in our case, any page load where the URL contains a query parameter like ?set_internal=true.
  5. Publish the workspace. Nothing in this setup requires a code deploy to the site. It's entirely contained in the GTM container, which is itself already loaded on every page through the snippet most sites already have installed.

Opting a browser out

To opt a browser out of tracking, anyone on the team visits automatonagency.com/?set_internal=true once, from their normal browser — not incognito, since the cookie needs somewhere to live. That single visit sets automaton_internal=true for two years. No IP list to maintain, no re-adding yourself every time your router reassigns an address.

Onboarding a new team member is a one-line instruction, not a support ticket: send them the link, they click it once from whatever browser they'll actually use day to day, done. We chose a two-year expiration deliberately — long enough that nobody has to think about it again for the life of most laptops, short enough that an abandoned browser profile eventually ages back into the real traffic count instead of silently excluding data forever.

What we considered and ruled out

Before building the cookie, we looked at three other approaches:

GA4's built-in Internal Traffic definitions. This is the IP-list feature described above. We didn't reject it out of preference — it's genuinely the right tool if your team works from a fixed set of static IPs. Ours doesn't, so it wasn't.

The Google Analytics Opt-out Browser Add-on. Google's official browser extension blocks GA from firing entirely. It works, but it's per-browser software every teammate has to separately install and keep enabled, which is a heavier ask than clicking a link once, and it's invisible to anyone auditing why a given session didn't report — there's no first-party record of who opted out or when.

localStorage instead of a cookie. We considered writing the internal-traffic flag to localStorage rather than a cookie, since it's simpler to read in some JavaScript contexts. We stuck with a cookie because GTM's native 1st Party Cookie variable type reads cookies directly with no custom JavaScript required, and cookies are visible to server-side requests if we ever need that flag outside the browser — localStorage isn't.

The trade-off

The trade-off worth naming: this excludes browsers, not people. Clear your cookies or switch to a new device and you're back in the count until you visit the link again. For a small team that's a two-second cost, paid once every couple of years — a much better deal than the IP list we were maintaining before. It's also worth watching for the inverse failure: if a real visitor somehow lands on a URL carrying that query parameter (shared accidentally, bookmarked from an internal test), their browser gets excluded too. We keep the parameter unpublished and off any page a client or visitor would ordinarily reach.

Why a four-tag GTM fix is worth writing up

Small mechanism, but it's the kind of fix that matters more than it looks like it should. Every dashboard we hand a client, every trend line we read internally — including the kind of before/after traffic reads we published in what we measured on our own site — is now measuring the people who actually showed up, not the five of us checking our own work. That distinction compounds: a client deciding whether a landing page redesign worked is reading the same session counts we are, and both of us are better off when neither number includes a designer refreshing the page eleven times during QA.

Frequently asked questions

Why does IP-based GA4 internal traffic exclusion stop working?

Most residential and mobile connections use dynamic IP addresses that change periodically, sometimes every few days. GA4's internal traffic list has to be manually updated every time an excluded IP changes, and in the meantime, that team member's visits count as real traffic. A first-party cookie doesn't depend on network address at all, so it doesn't break when an IP rotates.

Does this require any code changes to the website itself?

No. The entire mechanism lives inside the Google Tag Manager container, which is already loaded on every page through the standard GTM snippet most sites have installed. No deploy, no server-side change, nothing added to the site's codebase.

What happens if someone clears their cookies?

They go back to being counted as regular traffic until they revisit the opt-out link. For an internal team this is a rare, low-cost event — clicking one link again takes a few seconds, compared to the ongoing maintenance of an IP address list.

Can this same approach exclude a client's internal team, not just ours?

Yes — the mechanism is generic. Any site with GTM installed can add the same variable, trigger, exception, and Custom HTML tag, with the opt-out URL and cookie name set to whatever fits that property.

Why a two-year cookie expiration instead of a session cookie or a shorter window?

A session cookie would mean re-opting-out every time a browser is closed, which defeats the point. Two years is long enough that most people never think about it again during the working life of a laptop, while still eventually expiring so an abandoned or reissued device doesn't silently exclude data forever.

Published: August 2026.

Related: The Automaton stack · Is SEO dead


Keep reading
← All insightsStart a conversation →