simonridley/tracking-code-manager
A Laravel package for centrally managing tracking codes such as Google Analytics and other analytics snippets. Configure and inject tracking scripts without hardcoding them into your views, keeping analytics setup consistent and easy to update.
Install
composer require simonridley/tracking-code-manager
php artisan vendor:publish --tag=tracking-code-manager-config # check README for exact tag
Requires Statamic 4+ (v1.0.2 is the first release with the corrected CP route middleware — pin ^1.0.2 if you're on Statamic 4). The addon auto-registers its service provider; no manual config/app.php entry needed.
Where to look first
config/tracking-code-manager.php after publishing — this is where site/environment scoping and injection behaviour live.First use case Drop a GA4 measurement ID (or a raw GTM snippet) into the CP for your production site, then render it in your layout:
{{ tracking_code_manager }} {{# confirm the handle in the service provider #}}
placed just before </head>. Verify by viewing source on a cached-cleared page — not by trusting the CP "saved" toast.
CP-managed, template-rendered (recommended default). Let editors swap pixel IDs and add marketing tags without a deploy. Keep exactly one render call in your base layout so you never double-fire:
{{# resources/views/layout.antlers.html #}}
<head>
...
{{ tracking_code_manager }}
</head>
Environment gating. Configure the addon to be active only in production, or wrap the tag:
{{ if environment === 'production' }}
{{ tracking_code_manager }}
{{ /if }}
This keeps staging traffic out of analytics and prevents test conversions from polluting ad platforms.
Consent-gated loading. For GDPR-heavy projects, render the codes into a container the consent tool (Cookiebot, Klaro, Osano) can activate, or drive it from JS after consent:
<div id="tracking-scripts" data-consent="marketing">
{{ tracking_code_manager }}
</div>
Ship the scripts with type="text/plain" (or your tool's placeholder type) and let the CMP rewrite them. This is the pattern that survives an audit; firing tags unconditionally and "blocking" them client-side does not.
Multi-site / per-site codes. Statamic multisite is the common reason people reach for this addon over a hardcoded partial. Store distinct GA4/GTM IDs per site and confirm the tag resolves against the current site rather than the default — test by hitting each domain, not by switching sites in the CP.
Ordering for GTM + GA4. If both are present, GTM must load before any direct GA4 tag, otherwise you get duplicate pageviews from the GA4 config tag inside GTM plus the direct one. Keep GTM as the single source of truth where possible and use the addon only for what GTM can't own (server-side pixels, verification meta tags, custom scripts).
Blade projects. If you're not on Antlers, render the same output through the provided facade/tag class in a Blade component rather than duplicating the snippet — you want one code path so CP edits apply everywhere.
Testing. Assert the layout contains the expected ID in a feature test ($this->get('/')->assertSee('G-XXXXXXX')), and clear the addon/response cache in setUp so assertions don't read stale output.
The v1.0.2 fix matters on Statamic 4. CP routes were registered with the wrong middleware, which broke CP navigation/access for the addon's screens. If you're on 1.0.0/1.0.1 under Statamic 4, upgrade — and don't treat the CP loading sometimes as a permissions problem. If the CP page 403s or redirects oddly after install, check the version first.
Caching will bite you. Codes are resolved server-side; Statamic's static/cache layer can bake them into HTML. After editing a code, clear the relevant caches before concluding "it didn't save." On static-cached sites, plan a cache-clear into the editor workflow or the CP becomes a lie.
It's a script-injection tool — treat it as privileged. Anyone who can edit codes can execute arbitrary JS in every visitor's browser. Restrict the CP permission to a trusted role, and never expose the manager to front-end forms or unauthenticated endpoints. Review the rendered output for escaping: snippets are typically output raw, which is by design.
CSP breaks silently. A strict script-src will block injected snippets with only a console error. Add nonce/hashes for third-party domains, or you'll spend an afternoon debugging "the tag is in the source but analytics shows nothing."
Local/dev noise. It's easy to leave staging IDs firing on *.test. Set the config enabled/environment flag once in .env and verify APP_ENV is actually what you think — a surprising number of "why is GA4 full of localhost" tickets trace to this.
Don't stack managers. If the addon injects automatically and you also render the tag in the layout, every page fires twice. Pick one integration point and grep your layouts for duplicate calls.
Extension points. The service provider and config are the supported hooks: override the config for site mapping, and extend/replace the tag class if you need to transform output (e.g. wrapping in a CMP container or adding dataLayer context). Forking the view is the last resort since it drifts on upgrades.
Debugging checklist: view source (not DevTools "Elements") → confirm the tag is in the raw HTML → check for a CSP console error → confirm the correct site/environment resolved → clear caches → confirm consent state is "granted" at the time of firing. That order resolves nearly every "it's not tracking" report.
How can I help you explore Laravel packages today?