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

Technical Evaluation

Architecture fit — still a mismatch, and 1.2.1 makes the reason more concrete. freema/ga4-measurement-protocol-bundle remains a Symfony bundle, not a Laravel package: a Bundle class, a DI extension, a config tree (measurement_id, api_secret), and a service definition wrapping br33f/php-ga4-mp. Laravel ships Symfony components but not symfony/framework-bundle or a container wired the way the bundle expects. Installing it yields a config file you cannot consume and a service you cannot resolve.

What 1.2.1 actually tells us. The headline fix (#12) is that DefaultCustomUserIdHandler called Request::getSession(), which throws on stateless routes and API requests — so send() returned an error and the event was silently never sent. That is precisely the shape of a Laravel API/queue-worker request: no session. The patch is a good fix, but it confirms the bundle is architected around a sessionful Symfony HTTP request lifecycle, which is the opposite of where server-side GA4 events originate in a Laravel app. It also confirms the class of failure to expect from this stack: exceptions swallowed into a returned error, invisible in production, with no event delivered.

What is actually valuable here. Unchanged. The real capability lives in br33f/php-ga4-mp: a PSR-18-based client for POST /mp/collect with event/params builders. That library is the integration target; the bundle is a reference implementation of how to configure it.

Integration feasibility. Unchanged and straightforward: bypass the bundle, bind the library in a Laravel service provider — roughly 100–200 LOC for config, a singleton, a facade, and a queueable job.

Technical risk — movement on maturity, none on the fundamentals.

  • Maintenance signal improved marginally. 1.2.1 is a real, community-reported bug fix with a CI matrix across PHP 8.1–8.5 and Symfony 5.4/6.4/7.4/8.x, plus PHPStan 2 and PHPUnit 10.5. That is not an abandoned repo. It is still 1 star, 0 dependents, single maintainer — treat as sample code, not an inherited SLA.
  • Metadata anomaly — resolved. The changelog links 1.2.0...1.2.1, corroborating a sane release history. Still verify the previously future-dated 2026-10-02 tag resolves in your constraint set.
  • New: distribution no longer ships tests, CI, or dev files (#11). Sensible for a bundle, mildly negative for a package you may fork or audit — you can no longer run upstream's suite locally against your pinned version. Another argument for the internal-adapter boundary and your own tests.
  • New: silent coercion/drop of user_id. Non-string session user_id values (arrays, objects without __toString()) are now silently discarded rather than sent as "Array". Better than corrupting data, but it is a silent degradation. In your own adapter, validate and log the drop; don't inherit the silence.
  • Constraint collision: if the bundle requires symfony/framework-bundle, Composer will refuse or pull a parallel framework into Laravel. Confirm before any spike.
  • Credential handling: api_secret is a bearer-equivalent secret in a query string — secret manager only, rotatable without redeploy.
  • No delivery guarantees: MP returns 204 on accept, not on validation. 1.2.1 is a live demonstration that a client-side error can suppress the send entirely with no upstream signal. Route staging through /debug/mp/collect and handle validationMessages.
  • Consent/PII exposure: server-side events bypass client-side consent banners. Any user_data, user_id, or email/UUID-shaped param needs a documented lawful basis and a hashing story.

Key questions for the vendor/stakeholder before committing:

  1. What decision does server-side GA4 data change? If "nothing beyond gtag.js," stop here.
  2. Which events genuinely cannot be captured client-side (refunds, offline/phone conversions, renewals, backend-only funnel steps)? Usually 3–6 events, not 40.
  3. Does the property already have Enhanced Measurement and GA4→BigQuery export? If yes, the MP gap is narrower than it appears.
  4. Who rotates the api_secret, and what is the runbook?
  5. Are we prepared to own this code permanently, including the failure modes the upstream fix just surfaced? At 1 star with 0 dependents, yes by default.

Integration Approach

Recommendation: do not install the bundle. Adopt the pattern. 1.2.1 does not change this — it reinforces it. The bug that was fixed could only bite you if you were running the bundle inside a sessionless request path, which is exactly the Laravel case.

Stack fit. Target Laravel 10/11, PHP 8.1+, consume br33f/php-ga4-mp directly — or hand-roll the ~80-line client against POST https://www.google-analytics.com/mp/collect if the dependency's constraints go stale. The endpoint is stable enough that a thin hand-rolled client is legitimate and removes the transitive-dependency question entirely.

Laravel-shaped wrapper:

  • config/analytics.php — measurement_id, api_secret, enabled, debug, queue, timeout. Keep the bundle's key names for forward compatibility.
  • Ga4Client interface + MeasurementProtocolClient bound as a singleton in Ga4ServiceProvider.
  • SendGa4Event queued job with $tries = 3, exponential backoff, and a failed() dead-letter sink.
  • A Ga4 facade for ergonomics.
  • Explicit client_id on every call. Never derive identity from request/session state — that is the coupling that broke upstream, and it is the same coupling that will break you in queue workers and webhook handlers. When identity is absent or unusable, log it; don't silently drop.

Compatibility and migration path. Nothing in application code references third-party classes; everything goes through Ga4Client. That is a one-file swap if upstream is abandoned, if you move to server-side GTM, or if the endpoint changes. Upstream's own "Upgrading: no changes needed" confirms low churn — pinning is cheap either way.

Sequencing (each step is a gate):

  1. Measurement plan first. Enumerate server-side events, parameter schemas, and the reporting questions they answer. Analytics and legal sign off on the parameter list — this is where PII leaks in.
  2. Build the adapter against /debug/mp/collect in staging only, asserting validationMessages is empty for every planned event.
  3. Add queueing, retry, and rate limiting before the first production event. Not a follow-up ticket.
  4. Instrument and reconcile. Behind a feature flag, low-volume canary stream, reconciled against GA4 Realtime/DebugView for one full business cycle including a weekend.
  5. Roll out event-by-event, highest-value first (purchase/refund).

What not to do: don't boot the Symfony bundle inside Laravel via a shim, and don't vendor the bundle just to keep its config file.

Operational Impact

Maintenance. Better than the prior read: there is an active — if minimal — maintainer shipping fixes and widening the CI matrix (PHP 8.5, Symfony 8.x, PHPStan 2). There is still no community to inherit fixes from and no dependents, so budget for permanent internal ownership of a ~200-LOC adapter. Quarterly review: Google's MP changelog for breaking changes, confirm the pinned version still installs, confirm secret rotation actually happened. Note that upstream dist no longer carries tests — pin your own regression suite over the adapter.

Support. MIT, no vendor, no SLA, 0 dependents. Escalation is your team reading Google's MP docs. Acceptable for a small surface area; not a substitute for a maintained vendor integration. The 1.2.1 turnaround is a mild positive signal on responsiveness to issues.

Scaling. MP processes one event per HTTP request — no documented batch endpoint. Volume translates linearly into outbound requests:

  • Every event goes through a queue with a bounded worker pool; synchronous sending puts a vendor's latency in your request path.
  • Concurrency cap per GA4 property — bursts get throttled and throttled events are lost, not retried by Google.
  • No general client-side dedup; transaction_id dedupes purchases, but custom events sent from both gtag.js and MP double-count. One source of truth per event name, documented in the plan.
  • The 72-hour backfill window and ~4-hour attribution freshness window constrain replay — drain the dead-letter queue in hours, not days.

Failure modes to instrument explicitly:

Failure Symptom Mitigation
Invalid measurement_id / api_secret Silent 204, zero data Synthetic canary event + Realtime alert
Malformed params Silent drop in prod, visible only in /debug Debug endpoint in staging CI
Stateless route / no session (the 1.2.1 bug class) Exception swallowed, send() returns error, event never sent Don't use the bundle; pass client_id explicitly; alert on adapter error rate
Unusable identity silently discarded Data gaps attributed to "unknown" Validate and log drops rather than inheriting silent handling
Property throttling Data gaps under load Rate limiter + queue concurrency cap
Secret leaked in logs Query string in HTTP client debug output Redact query params in HTTP middleware
Consent violation Regulatory, not technical Parameter allowlist enforced in code
Upstream abandoned / tests removed from dist PHP upgrade block, no local audit Internal interface boundary + hand-rolled fallback + own test suite
Clock skew / bad timestamps Events outside 72h window discarded Server-side timestamp_micros from NTP-synced host

Ramp-up. Low. A mid-level Laravel engineer builds and validates the adapter in 2–4 days, plus 1–2 days of analytics/legal review for the measurement plan. The bundle saves perhaps half a day of scaffolding.

Overall verdict. Revised from 19/100 to 22/100: +for a genuine, community-reported fix and a modern CI matrix; −for 1 star, 0 dependents, single maintainer, and tests no longer shipped. 1.2.1 is a good patch to a package you should still not install. The protocol is worth adopting for a narrow, well-justified set of server-side events. The bundle is not. Take the config shape and the library choice as guidance, build a 200-line Laravel-native adapter behind an internal interface, pass client_id explicitly, and accept that ownership of the integration is yours from day one.

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.
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle