awcodes/gtm
A lightweight Laravel and Filament package for adding Google Tag Manager to your app. Set your GTM container ID, inject the required scripts, and push dataLayer events without editing your Blade views—a quick, clean analytics setup.
What is being assessed. awcodes/gtm is a single-purpose Laravel package that renders the two halves of a Google Tag Manager container into a Blade layout: x-gtm::script (the gtm.js loader, placed in <head>) and x-gtm::no-script (the <noscript> iframe, placed after <body>). It is driven by a published config/gtm.php exposing gtm.id and gtm.enabled, plus a per-component enabled prop. That is the entire surface area.
Architecture fit. Idiomatic and deliberately thin. No migrations, routes, middleware, queue workers, jobs, or meaningful service-provider logic. Output is static HTML emitted at render time, which puts it in the "cheap to adopt, cheap to remove" category — if the maintainer walks away, replacement is roughly an hour of pasting the same snippet into a layout partial. The real architectural fork is not "adopt a 5-star package" but "hand-maintain GTM snippet HTML and container IDs across every Blade layout, or standardise on one auditable component with a published config." The package makes standardising the cheaper path, which is the strongest argument in its favour. The boundary to state plainly: it renders tags; it does not define event schemas, validate the data layer, do server-side tagging, or push page_view on SPA transitions.
Integration feasibility. Low effort, one design point that needs a product decision. Adoption is composer require, publish config, wire gtm.id to an env var per environment, drop two components into the layout. The design point is the two-level enable gate: tags render only when both the per-component enabled prop and the gtm.enabled config value are true. This is a conservative safety model — config is the environment kill switch (local/CI/staging dark), the prop is the surface switch (app vs. admin vs. Filament panels, per tenant) — but it is also a silent-failure vector. A correctly wired deployment that renders nothing because one of two keys is false produces no error, no log line, and no failing test. v2.0.1 is the first release where the rule is explicitly documented in a new docs/ directory, which materially reduces the "why is there nothing in the page source" support load and lowers the bus factor on tag management.
Technical risk. Low blast radius, moderate verification signal. The v2.0.1 defect is a one-line config-key error: x-gtm::no-script read config('site.gtm.id') while the package publishes config/gtm.php, and no site namespace exists — so the fallback iframe emitted an empty container ID and reported nothing for visitors without JavaScript. x-gtm::script was unaffected, which is precisely why it survived: the prior suite rendered both components together and asserted the container ID appeared somewhere in combined output, which the healthy script tag satisfied on its own while the inert iframe passed beside it. That is a false-positive test, not a missing one. v2.0.1 replaces it with per-component assertions, a full matrix over enabled prop × gtm.enabled config, and a fix to the test environment (gtm.active vs gtm.enabled). Both are green flags for maintainer rigour, and both mean any pre-2.0.1 version carries less verification signal than its green badge implied — directly relevant if v2.0.0 was already certified internally.
What the bug cost the business. Every visitor without JavaScript was invisible to GTM: privacy-hardened browsers, link-preview and render services, older clients, some crawler/tooling traffic. The volume loss is thin but the trust implication is not — any historical baseline for "no-JS share of sessions" is an artefact of a broken tag, not a measurement, and must not be used to size capacity, argue channel mix, or set funnel-completion expectations. Expect a step change in reported volume for that segment after upgrade; brief dashboard owners before the deploy lands so it is not read as growth or as a tracking regression.
Key questions.
site.gtm.id config key or override the no-script view as a workaround? Both must be deleted in the same commit as the upgrade or we retain a dead key and a view that drifts from corrected upstream.page_view pushes elsewhere.Stack fit. Blade-first Laravel. The components compile into views and read from config, so they compose with any layout structure — application shell, admin panel, Filament panel — without per-template negotiation. Two operational realities follow from that: package auto-discovery must remain enabled (or the component namespace be registered manually), and config is cached in production, so config:cache must be rebuilt on every deploy. A stale cached gtm.id will mask both an environment change and the v2.0.1 fix itself.
Migration path. Two distinct migrations, and they should not be conflated:
Rollback is genuinely cheap in both cases: set gtm.enabled=false for an immediate kill switch, or revert the layout partial. That is worth stating to stakeholders — the blast radius of a bad deploy here is "no analytics for an hour," not "site down."
Compatibility. No stated Laravel/PHP support matrix in the release notes. Mitigate by pinning an exact constraint in composer.json (≥ 2.0.1, no wildcards, no dev branches), verifying against our specific Laravel/PHP pair in CI, and treating major-version bumps as review events rather than automatic merges. Five stars, zero dependents, and single-maintainer cadence are acceptable for a snippet renderer and disqualifying for anything load-bearing — this package must stay on the "rendering" side of that line.
Sequencing.
config/gtm.php, wire gtm.id to an env var, default gtm.enabled=false in .env.example.gtm.enabled=false and assert nothing renders. Then flip true and assert both the head script and the body noscript iframe contain a non-empty container ID.site.gtm.id shim and any overridden no-script view in the same commit.page_view pushes — none of which this package addresses.Maintenance. Near-static dependency; the risk is abandonment, not churn. Quarterly dependency triage is sufficient, with one guardrail: never certify a release on a green badge alone — check that per-component tests exist for the paths we rely on. If the package drops off, the exit is a layout partial containing the same two snippets, so the maintenance question is really "who owns the snippet," not "who owns the vendor relationship."
Support. The dominant support question will be "why is there no GTM markup on this page," and the answer is almost always the two-key gate. The new docs/ lowers that burden, but the internal runbook should be explicit and short: check gtm.enabled config, check the enabled prop, check whether config cache is stale, view source, check for a local override. This also answers a staffing question — a generalist Laravel engineer can own tag rendering in an afternoon — and separates it from the analytics engineering question, which is a different skill set.
Scaling. Negligible runtime cost: a view render and two config lookups per request; no external calls at render time (gtm.js loads client-side). It will not be a bottleneck at any traffic level we are likely to see. One scaling caveat worth flagging early: gtm.id is global config, so per-tenant or per-brand container IDs would require runtime config overrides. That is the point where the package stops being sufficient and we should expect to write our own thin wrapper.
Failure modes and mitigations.
gtm.enabled=false as the default in all non-production env files.Ramp-up. Low. One engineer afternoon for a Laravel-fluent developer, less with the docs. The honest risk in ramp-up is a false sense of completion: the package is simple, so adopting it feels like "tracking is done," when in fact deciding which events exist, verifying they fire, and honouring consent remain entirely ours. Sequence the dependency work as a prerequisite and budget the analytics discipline separately — otherwise we ship a correctly rendered container that measures the wrong things.
How can I help you explore Laravel packages today?