Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Ga4 Measurement Protocol Bundle Laravel Package

freema/ga4-measurement-protocol-bundle

Symfony bundle for sending server-side events to Google Analytics 4 via the Measurement Protocol, with straightforward configuration for tracking conversions and user activity from your PHP application.

View on GitHub
Deep Wiki
Context7

Product Decisions This Supports

Feature and roadmap decisions around server-side measurement. This package is a client for GA4's Measurement Protocol — the HTTP endpoint that lets your backend send events to GA4 without a browser. That unlocks a specific, valuable slice of the roadmap:

  • Backend-only conversions. Purchases, subscription renewals, refunds, dunning recoveries, trial starts, closed-won CRM deals — events that either never happen in the browser or happen hours/days later.
  • Offline and delayed conversion upload. Sync CRM or billing-system outcomes back to GA4 so paid and organic attribution reflects actual revenue, not just form submits.
  • Ad-blocker and ITP resilience. Server events bypass client-side blocking and cookie restrictions, recovering a measurable share of lost conversion signal. This is usually the number that justifies the work.
  • Consent-aware emission. The decision to send an event lives in backend code where you actually know the user's consent state. Note: Measurement Protocol itself does not enforce consent — you gate emission yourself, which is arguably the right place for it.
  • Deduplication strategy. Passing the same client_id + session_id + event name lets you reconcile server events against client-side gtag events, or intentionally route a given event to one path only.

Build vs. buy. The underlying work is a signed POST to /mp/collect with api_secret, client_id, and an events array. That is genuinely a one-to-three-day build. The package's value is typed payload construction, DI configuration, and a testable seam. Treat it as a thin adapter, not a platform.

What this is not. It does not read data. It does not replace the GA4 Data API, BigQuery export, or a CDP. It is one-way, fire-and-forget event ingestion.

Caveat on fit: despite being filed under a Laravel/PHP search, this is a Symfony bundle. In Laravel you'd either use the underlying client library standalone or bind it into the container yourself. Budget a half-day for that adapter work — and note that the bundle's default user-ID handler is built on Symfony's Request::getSession(), which has no Laravel equivalent. You will be writing your own handler regardless (see below).

When to Consider This Package

Consider it when:

  • You're on Symfony, or willing to wrap the standalone client in a Laravel service provider, and you want a DI-configured, typed Measurement Protocol client rather than hand-rolled Guzzle calls.
  • You already use GA4 and need server events without standing up a CDP. Below roughly a few hundred thousand events/month, the CDP tax isn't worth it.
  • You have a small team with no appetite to maintain HTTP retry logic and payload schemas themselves.
  • You want the send path behind an interface you can later swap for Segment/RudderStack/GA-direct without touching call sites.
  • You pin to ^1.2.1 or later. See the release note below — earlier versions silently dropped every event originating outside a session.

Look elsewhere (or write it yourself) when:

  • Supply-chain risk is a blocker. Still one star, zero dependents, single maintainer. For a payments-adjacent analytics path, that's a real review item — vendor it, pin the commit, or fork. Note the tradeoff introduced in 1.2.1: tests and CI files are no longer shipped in the Composer archive, so a vendored copy is harder to audit in place. Pull the tagged source from GitHub (not the dist zip) if you need to review or run the suite yourself.
  • The release metadata still looks wrong. The previously listed last release date (2026-10-02) remains anomalous. The 1.2.1 changelog is concrete and reproducible (real issue references, a compare link, an explicit upgrade note), which is reassuring, but verify tag authenticity and provenance before adopting. Don't let a tooling artifact become a dependency.
  • You're on Laravel and want first-class support. There is still no Laravel package here. Use the standalone client or a ~150-line HTTP facade.
  • You need operational rigor the package won't give you. Measurement Protocol returns 204 and silently drops malformed events. Release 1.2.1 removes one internal cause of silent drops, but the protocol-level one remains: you need your own validation pass against /debug/mp/collect, plus queueing, retries, and a dead-letter log. If those are must-haves, the wrapper saves you little.
  • You need batching beyond 25 events per request, guaranteed delivery, or per-event status. That's queue infrastructure, not a client library.
  • You need reporting. Use the GA4 Data API or BigQuery export.
  • You're high-volume or enterprise. Buy the CDP or build against Google's endpoint directly with proper infra ownership.

Release 1.2.1 in context — what changed and why it matters:

  • Fixed a silent-drop bug that hit precisely the primary use case. DefaultCustomUserIdHandler called Request::getSession(), which throws on stateless routes and API requests; the resulting error meant send() failed and the event was never transmitted. Server-side conversion events overwhelmingly come from queues, webhooks, and API endpoints — the exact routes that had no session. Anyone who piloted this package on a queued purchase event before 1.2.1 may have been measuring zero, not a conversion rate. Validate any prior pilot data before drawing conclusions from it.
  • The handler no longer coerces bad IDs into garbage. A session user_id that is an array or a non-stringable object was previously sent as the literal string "Array" — polluting user-level reporting. It is now ignored. Good hygiene, but it also means the package will quietly decline to attach a user ID rather than complain; instrument that.
  • Packaging and CI hygiene improved. Tests/CI/dev files no longer ship in the Composer archive (smaller install surface), and CI now covers PHP 8.1–8.5 against Symfony 5.4, 6.4, 7.4 and 8.x, with PHPStan 2 and PHPUnit 10.5. That is a genuine maintenance signal — the project is being kept current across the Symfony majors, which materially reduces the "abandoned dependency" risk you'd otherwise carry.
  • Non-breaking. No changes required to upgrade; this is a patch. There is no reason to remain on 1.2.0 other than lockfile inertia.

How to Pitch It (Stakeholders)

For executives — one paragraph: A low-cost way to stop losing conversion signal to ad blockers and to get backend-only revenue events (renewals, refunds, offline closes) into GA4, so the attribution numbers we report match the numbers finance sees. Effort is days, not weeks. The latest patch fixed a defect that had been silently suppressing events from backend and API routes — the very events we care about — and the project is now actively tested across current PHP and Symfony versions. The residual risk is that it's a tiny open-source dependency with a single maintainer, so we mitigate by wrapping it behind an internal interface and vendoring the code — we can replace it without touching product code.

For engineering — the actual ask: Adopt this as a thin adapter, not a framework, and pin ^1.2.1 as the floor. Concretely:

  1. Define an internal AnalyticsClient interface (track(event, userId, properties)); implement it with this package.
  2. Configure measurement_id and api_secret via env, never in code.
  3. Do not rely on DefaultCustomUserIdHandler. It is session-bound and unusable from Laravel API routes, queue workers, and jobs — the exact contexts we need. Implement your own user-ID handler (or pass user_id explicitly per event) and register it. This is also the class the 1.2.1 fix touched, so keep our usage on the explicit side of it.
  4. Plumb client_id/session_id from the existing _ga cookie where present; generate and persist a first-party ID where it isn't. Treat absence explicitly rather than letting the client omit the field.
  5. Add a queue + retry + structured logging wrapper. Assume every event can be silently dropped and log accordingly — including the newly quiet "no user ID resolved" path.
  6. Validate payloads in staging against the /debug/mp/collect endpoint before enabling in production.
  7. If we vendor, pull the tagged source from GitHub, not the Composer dist — 1.2.1 strips tests and CI from the archive, so the dist is not reviewable.

Sequencing recommendation: Ship one high-value event end-to-end first — purchase, deduplicated against the existing client-side gtag event — verify in GA4 DebugView and Realtime from a queued/job context, not a controller, confirm no double-counting for one full billing cycle, then expand to renewals and refunds. That single slice is what proves or kills the business case; everything after it is repetition. If a prior pilot was run on 1.2.0, re-baseline it before quoting results.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource