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

Backend Google Analytics Bundle Laravel Package

modera/backend-google-analytics-bundle

A backend bundle for Modera applications that integrates Google Analytics into the admin panel, providing configuration and reporting widgets so teams can view traffic and site metrics alongside their existing management tools.

View on GitHub
Deep Wiki
Context7

Getting Started

Read this first: the package is a Symfony bundle, not a Laravel package. modera/backend-google-analytics-bundle registers a Symfony DI extension, tags kernel event listeners, and depends on Modera's own backend-bundle for its admin shell. There is no service provider, no facade, no config/ to publish. composer require modera/backend-google-analytics-bundle into a Laravel app will install files you cannot boot. Treat the repo as a reference implementation of a pattern, not a dependency.

Where to look in the source (10–15 minutes):

  1. DependencyInjection/ — shows which services are wired and, critically, how the tracker is injected (client-side JS vs. server-side hit).
  2. The listener/subscriber that fires on backend routes — this is the "is this an admin pageview?" logic you'll reimplement.
  3. Resources/views/ — if there's a Twig template emitting a ga() snippet or gtag.js tag, note the GA protocol. Modera bundles of this vintage typically target Universal Analytics (UA-*), which stopped processing hits in 2023.
  4. composer.json — check the Symfony version ceiling and whether it depends on modera/backend-bundle. That dependency alone is a blocker.

First use case to build (in Laravel, ~100 lines):

Instrument authenticated admin routes with a queued pageview to GA4's Measurement Protocol v2.

php artisan make:middleware TrackAdminPageview
php artisan make:job SendAdminPageview
// app/Http/Middleware/TrackAdminPageview.php
public function handle(Request $request, Closure $next)
{
    $response = $next($request);

    if ($request->user() && $request->is('admin/*') && $request->isMethod('GET')) {
        SendAdminPageview::dispatch(
            routeTemplate: $request->route()->getName() ?? $request->path(),
            role: $request->user()->role,
            sessionId: hash('sha256', $request->session()->getId()),
        )->onQueue('analytics');
    }

    return $response;
}
// app/Jobs/SendAdminPageview.php
Http::timeout(3)->post(
    'https://www.google-analytics.com/mp/collect?measurement_id='.config('analytics.measurement_id')
    .'&api_secret='.config('analytics.api_secret'),
    [
        'client_id' => $this->sessionId,
        'events' => [[
            'name' => 'page_view',
            'params' => [
                'page_location' => config('app.url').'/'.$this->routeTemplate,
                'page_title'    => $this->routeTemplate,
                'user_role'     => $this->role,
            ],
        ]],
    ]
);

Register in bootstrap/app.php (->withMiddleware(...)) or Kernel::$middleware, gated by a feature flag. Validate against the GA4 debug endpoint (/debug/mp/collect) before enabling it in production.

Implementation Patterns

Pattern 1 — Middleware, not a listener. The bundle's Symfony kernel.event_listener wiring is unnecessary in Laravel. One web-group middleware with a route filter is equivalent and far easier to test.

Pattern 2 — Route templates, never raw URLs. Send admin.users.index, not admin/users/4821?filter=email. Admin URLs routinely leak record IDs, customer names, and account numbers into GA. Route names also give you a stable, low-cardinality dimension for reporting.

Pattern 3 — Queue everything. Dispatch to a dedicated analytics queue with its own worker so a slow Google endpoint cannot starve transactional jobs. Set timeout(3) and ->onFailure(fn () => null) — analytics must never break an admin request.

Pattern 4 — Custom dimensions via user_role, team, env. In GA4 these must be registered as custom dimensions in the property admin before hits arrive, or they are silently dropped. Register user_role and env (staging hits will otherwise pollute production reporting — or use separate measurement IDs per environment).

Pattern 5 — Client ID synthesis. GA4 requires a client_id. The Laravel session ID (hashed) is a reasonable proxy and stays stable per session. Do not use the user's email or database ID — it's PII flowing to a US endpoint.

Pattern 6 — Feature-flag and environment-gate. Put it behind config('analytics.backend_tracking_enabled') defaulting to false. Enable per environment: local → off, staging → debug endpoint, production → live.

Pattern 7 — If you must have client-side parity with the bundle's approach, the gtag.js snippet can go into your admin layout with a CSP nonce. But server-side MP is preferable in Laravel: no CSP surface, no ad-blocker interference, no dependency on client JS, and you control the payload.

Testing:

Http::fake();
$this->actingAs($admin)->get(route('admin.users.index'));
Http::assertSent(fn ($req) => str_contains($req->url(), '/mp/collect')
    && $req['events'][0]['params']['page_location'] !== null);

Gotchas and Tips

The GA protocol is the biggest unknown. Before copying anything from the bundle, confirm whether it emits UA-* (dead since July 2023), gtag.js, or GA4 MP v2. If it's UA-era, nothing about its payload shape is reusable — you'd be porting a corpse.

Silent failure is the default. GA4's MP returns HTTP 2xx with an empty body on a revoked api_secret, a wrong measurement_id, or a malformed event. You will ship broken analytics and never know. Mitigate by routing ~1% of hits to https://www.google-analytics.com/debug/mp/collect (which returns validation messages) and logging non-valid responses.

client_id and IP handling. GA4 MP does not accept IPs in the payload — geo comes from the request origin. If the job runs from a queue worker, the origin IP is your server's, not the user's. Either pass a geo override or accept degraded geo data. Never send user_id as a raw identifier.

Duplicates will inflate your numbers. Livewire navigation, Inertia partial reloads, Turbo prefetch, and multi-tab sessions all trigger page_view more than once. Dedupe on (session_id, route, floor(time/30)) via Cache::add() with a short TTL — cheap and effective.

Egress firewalls and CSP. If your admin runs on an internal-only network or a restrictive connect-src, client-side dispatch to google-analytics.com will fail silently. Server-side MP from a queue worker is more reliable but still needs outbound HTTPS allowed.

Privacy is not a follow-up. Backend pageviews are personal data about employees. In the EU this needs a DPA and often works-council sign-off. Strip query strings, strip record IDs, use route templates, and confirm data residency before enabling. This is the single most likely reason the project stalls after the code is written.

Vendor/fork risk. Zero stars, zero dependents, no issue triage, no docs beyond the repo. If you fork and port it, you own 100% of maintenance for ~100 lines of logic — strictly worse than writing those 100 lines yourself.

Extension points worth copying as ideas (not code): the bundle separates credential config, tracker injection, and dashboard-widget registration. That separation is worth mirroring — config/analytics.php for credentials, a middleware for emission, and a query layer (BI tool, Metabase, or a simple admin_pageviews table) for reporting. Keeping emission and reporting decoupled means you can swap GA for Matomo, Plausible, or a first-party table without touching request-path code.

When to walk away entirely. If the real requirement is funnels, retention, or per-user journeys, admin pageviews in GA are the wrong tool regardless of package. A first-party events table plus a BI dashboard beats GA on privacy, ownership, and query flexibility — and is the same ~1 sprint of work.

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