liberusoftware/analytics-google
A Laravel package for integrating Google Analytics into your application, with configuration and convenient helpers to send tracking data and retrieve analytics insights with minimal setup.
This is a low-adoption package (0 stars, 0 dependents, single maintainer), so treat the source as the documentation. Budget an hour to read src/ before wiring it into anything user-facing.
Install and publish
composer require liberusoftware/analytics-google
php artisan vendor:publish --tag=analytics-google-config
Where to look first, in order
config/analytics-google.php — the measurement ID(s), the consent resolver binding, and the queue/transport settings live here. This file tells you more about the package's design than the README does.src/ — look for the mapper/registry class (the thing that turns your application events into GA4 event payloads) and the consent gate. Those two classes are the entire public contract in practice.tests/ — with no dependents and no blog posts, the test suite is your only worked example. Copy the fixtures.composer.json — confirm which Laravel and PHP versions are actually supported rather than trusting the description. Note the 2026-10-27 release date: if that is in the future relative to your install, you are pinning a pre-release or a mis-tagged version, so pin an explicit commit or tag.First use case
Do the smallest possible loop: one event, consent granted, visible in GA4 DebugView.
// config/analytics-google.php
'measurement_id' => env('GA4_MEASUREMENT_ID'),
'api_secret' => env('GA4_API_SECRET'),
'consent' => [
'resolver' => App\Analytics\ConsentResolver::class,
'default' => false, // fail closed
],
app(AnalyticsGoogle::class)->track('signup_completed', [
'plan' => 'pro',
'method' => 'google',
]);
Then open GA4 → Admin → DebugView and confirm the event lands before you build anything on top of it. If it does not appear, the problem is almost always the consent resolver returning falsy — check that before you touch the mapping code.
Domain events → analytics events, not controller calls
The healthy pattern with this kind of adapter is a listener that reacts to a domain event rather than scattering track() calls through controllers. Keep the translation in one place:
final class TrackOrderPlaced
{
public function __construct(private AnalyticsGoogle $analytics) {}
public function handle(OrderPlaced $event): void
{
$this->analytics->track('purchase', [
'transaction_id' => $event->order->reference,
'value' => $event->order->total / 100,
'currency' => $event->order->currency,
'items' => $event->order->lines->map(fn ($l) => [
'item_id' => $l->sku,
'item_name' => $l->name,
'quantity' => $l->qty,
'price' => $l->unitPrice / 100,
])->all(),
]);
}
}
This gives you a single seam to add consent checks, sampling, or a second destination later.
Queue everything
Network calls to GA have no business in the request cycle — a slow or failing Google endpoint should never delay a checkout. Register the listener on a queue and give it $tries = 3 with a backoff. Make dispatch idempotent using the transaction/event ID so retries don't double-count.
Consent gating belongs in the package, but verify it
"Consent-aware" is the package's entire reason to exist, so test it rather than assuming:
public function test_no_event_is_sent_without_consent(): void
{
$this->mock(ConsentResolver::class)->shouldReceive('allows')->andReturn(false);
AnalyticsGoogle::fake();
event(new OrderPlaced($order));
AnalyticsGoogle::assertNothingTracked();
}
If the package has no fake()/spy helper, build a thin wrapper interface in your app and fake that instead — do not let untestable static calls metastasise through your codebase.
Server-side Measurement Protocol as the primary path
Server-side dispatch survives ad blockers and ITP, which is a real argument for this package over the client-side gtag snippet. The trade-off: you supply client_id yourself (from the _ga cookie or a stored first-party ID). Decide early where that ID is minted and persisted, because retrofitting it is painful.
Keep the mapper open for extension
Map GA4 names explicitly rather than deriving them from PHP class names. If the package offers a mapper registry, bind your own implementation in a service provider and override only what differs; do not fork the package for naming differences.
Consent defaults must fail closed. If the resolver throws (user record missing, cookie absent, service down), the safe behaviour is "do not send". Audit the exception path — a swallowed exception that defaults to true is the single worst bug this class of package can have.
GA4 naming rules are enforced silently. Event names must be ≤40 chars, start with a letter, and use only alphanumerics and underscores; parameter names ≤40 chars; 25 parameters per event; values ≤100 chars. Overruns are dropped without an error. Add a test that asserts every event name and param your app emits against those limits.
Reserved names and automatic events. GA4 rejects some event names (session_start, first_visit, page_view is special-cased) and silently ignores event_name as a parameter. The Measurement Protocol also ignores payloads carrying a timestamp more than 72 hours old — relevant if you replay a queue after an outage.
debug_mode is not a config flag you leave on. DebugView only populates when the event carries debug_mode: 1. Some adapters expose this as a config toggle; verify it is env-scoped and off in production.
Measurement Protocol needs both api_secret and client_id. Missing api_secret returns a 2xx with a validation error in the body — check the response body, not just the HTTP status. Most "it silently does nothing" reports against packages like this trace back to that.
Zero adoption means zero external answers. There is no Stack Overflow corpus, no issue history, and no third-party write-ups. Consequences: pin an exact version or commit in composer.json; wrap the package behind your own one-method interface so a swap is a one-file change; and be prepared to read the vendor source when behaviour surprises you.
Watch the release cadence and the date. A single recent tag with no follow-up means bug fixes are yours to write. Fork-and-patch is a legitimate plan here — decide in advance whether you are willing to maintain a fork, and if not, keep the integration surface as narrow as the wrapper allows.
Extension points worth knowing before you need them: the consent resolver binding (swap it for your CMP), the transport/client binding (swap it for a fake in tests, or for a batching client), and the event mapper (swap it for your naming conventions). Almost everything else in a package this size is internal and should be treated as private.
Verify what "consent-aware" means for this package specifically. Consent Mode v2 distinguishes analytics_storage from ad_storage from ad_user_data/ad_personalization. Confirm which signals the package actually inspects; if it only checks one, you are on the hook for the rest at your call site.
How can I help you explore Laravel packages today?