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

Google Analytics Server Side Tracking Bundle Laravel Package

setono/google-analytics-server-side-tracking-bundle

A Symfony bundle for sending Google Analytics events server-side via the Measurement Protocol, helping you capture purchases, conversions, and interactions from your backend even when client-side JavaScript tracking is blocked or unavailable.

View on GitHub
Deep Wiki
Context7

Getting Started

This is a Symfony bundle, not a Laravel package. For a Laravel codebase, treat it as a reference implementation and a source of reusable tracking concepts rather than a drop-in dependency.

  1. Look first at the README and bundle wiring

    • README.md for configuration keys and example usage.
    • Setono\GoogleAnalyticsServerSideTrackingBundle\SetonoGoogleAnalyticsServerSideTrackingBundle for registered services.
    • DependencyInjection/Configuration.php for the real config tree — as of v0.3.9 this includes a configurable user agent filter block.
    • The underlying setono/google-analytics-server-side-tracking library, if present, for the actual Measurement Protocol client.
    • Tests/examples for expected event payload shapes.
  2. Install only if you can isolate Symfony dependencies

    composer require setono/google-analytics-server-side-tracking-bundle
    

    In Laravel, do not expect package discovery, config publishing, or a Laravel service provider. You will need an adapter or a small port around the underlying client.

  3. First practical use case Track a high-value server-side event that client-side gtag often misses: order completion, lead submission, or signup.

    Conceptually:

    // Pseudocode: wrap the bundle/library tracker behind your own contract.
    $tracker->trackPurchase(
        clientId: $clientId,
        transactionId: $order->number,
        value: $order->total,
        currency: 'USD',
        items: $order->items->map(...)->all(),
    );
    
  4. Laravel integration path

    • Create App\Contracts\AnalyticsTracker.
    • Implement it with either:
      • a thin wrapper around the package’s tracker service in a Symfony context, or
      • a direct GA4 Measurement Protocol client using Laravel’s HTTP client.
    • Bind it as a singleton in AppServiceProvider.
    • Dispatch tracking through queued jobs/listeners, not directly in controllers.

Implementation Patterns

1. Adapter/wrapper pattern Keep the package’s API out of your domain code:

interface AnalyticsTracker
{
    public function trackPurchase(Order $order, string $clientId): void;
    public function trackEvent(string $name, array $params, string $clientId): void;
}

final class Ga4MeasurementProtocolTracker implements AnalyticsTracker
{
    public function __construct(private ClientInterface $client) {}

    public function trackPurchase(Order $order, string $clientId): void
    {
        $this->client->post('/mp/collect', [
            'client_id' => $clientId,
            'events' => [[
                'name' => 'purchase',
                'params' => [
                    'transaction_id' => $order->number,
                    'value' => (float) $order->total,
                    'currency' => $order->currency,
                    'items' => $order->items->map(fn ($item) => [
                        'item_id' => $item->sku,
                        'item_name' => $item->name,
                        'price' => (float) $item->price,
                        'quantity' => $item->quantity,
                    ])->all(),
                ],
            ]],
        ]);
    }
}

2. Resolve and persist client_id in middleware GA4 Measurement Protocol needs a stable client_id. Read it from the _ga cookie when available:

// _ga=GA1.1.123456789.1700000000 -> client_id "123456789.1700000000"
$clientId = $request->cookie('_ga')
    ? substr($request->cookie('_ga'), 6)
    : (string) Str::uuid();

Store it in the session or a first-party cookie so queued jobs can use it later. For logged-in users, also send user_id.

3. Queue all tracking calls Never let a GA Measurement Protocol HTTP call block a checkout or form submission.

event(new OrderPaid($order)); // listener dispatches TrackPurchaseJob

Use Laravel queues with:

  • $tries = 3 for network/5xx failures,
  • backoff,
  • a timeout,
  • idempotency checks so retries do not duplicate transaction_id records.

4. Domain event -> listener -> job

final class DispatchPurchaseTracking
{
    public function handle(OrderPaid $event): void
    {
        TrackPurchaseJob::dispatch($event->order->id);
    }
}

This keeps controllers thin and makes tracking testable and observable.

5. Filter on user agent, deliberately (new in v0.3.9) The bundle now ships an empty user agent filter and a configurable user agent filter. Use this pattern to discard bot/CLI/health-check traffic — and to explicitly decide what happens when a queue worker sends no UA at all. In a Laravel wrapper, mirror it:

if (blank($userAgent) && $config->rejectEmptyUserAgent) {
    return; // don't send a phantom event
}

Make the filter configurable per environment; an over-broad bot list will silently swallow legitimate events.

6. Consent and privacy gate Place a TrackingConsent check before dispatch:

if (! $consent->allowsAnalytics($request)) {
    return;
}

Server-side tracking does not remove GDPR/CCPA obligations. Avoid sending raw PII; hash emails if you must send them as sha256_email_address.

7. Testing pattern Fake the tracker contract in unit tests and assert domain events/jobs:

$this->mock(AnalyticsTracker::class)
    ->shouldReceive('trackPurchase')
    ->once();

For integration validation, send to the GA4 debug endpoint during local development:

https://www.google-analytics.com/debug/mp/collect

It returns validation messages that the production endpoint does not return.

8. Integration tips

  • Map domain events to GA4 event names such as purchase, generate_lead, sign_up.
  • Include engagement_time_msec and session_id when you want events to appear in sessions/reports.
  • Add debug_mode => 1 in non-production to inspect events in GA4 DebugView.
  • Use a dedicated measurement_id and api_secret per environment.
  • Prefer a Laravel-native wrapper or direct measurement client if Symfony dependency conflicts appear.

Gotchas and Tips

  • Not a Laravel package. The bundle targets Symfony’s kernel/bundle registration. Laravel has no equivalent bundle auto-registration, and there is no Laravel service provider or publishable config. You will need an adapter or direct client.

  • Symfony request coupling. The bundle may rely on symfony/http-foundation and RequestStack. Laravel’s Illuminate\Http\Request is not the same object. Do not assume a controller-injected Laravel request will satisfy the bundle’s services.

  • Empty user agent is now filtered by default (v0.3.9). This is the headline change. It is good hygiene for bots, but queue workers, console commands, and some HTTP clients send no UA — those events may now be dropped silently if you rely on the bundle’s filtering. Pass the originating browser’s user agent explicitly through your job payload, or disable/loosen the filter in that path.

  • The configurable UA filter is a double-edged sword. A custom regex/allow-list that is too strict will silently discard traffic. Start permissive, log filtered events (or emit a metric), and only tighten once you have data. Treat it as a routing decision you can observe, not a black box.

  • Composer conflicts are likely. Laravel already depends on many Symfony components. The bundle constrains Symfony/PHP versions in ways that can conflict with Laravel 10/11 and PHP 8.2/8.3. Check composer why-not before committing.

  • Maintenance risk is lower but not gone. v0.3.9 is a patch release on a 0.x line with small, focused changes — a healthy sign of active maintenance, but still 0.x with low adoption. Pin the version, inspect the dependency tree, and keep the option of replacing it with a small in-house Measurement Protocol client.

  • GA4 vs Universal Analytics. GA4 Measurement Protocol is not the old UA protocol. Use measurement_id plus api_secret; do not copy UA-era payloads. Verify the package’s target protocol before building around it.

  • client_id is easy to get wrong. Server-side requests do not have a browser-generated GA client ID unless you read _ga or generate and persist one. Missing or unstable client IDs lead to fragmented sessions and missing reports.

  • Production endpoint returns 204 even for some invalid payloads. Do not treat 204 as proof that the event was accepted — and don’t treat a missing event as a bug before checking whether the new UA filter dropped it. Use the /debug/mp/collect endpoint locally and inspect GA4 DebugView.

  • Retries can double-count. HTTP libraries may retry, and your queue may also retry. Use a database marker or unique job constraint for purchase-like events. Do not retry validation/4xx errors blindly.

  • Queue failures are silent unless monitored. Add logging/metrics around tracking jobs. A failed analytics job should not fail checkout, but it should be visible.

  • Consent still applies. Server-side tracking does not bypass cookie consent laws. Gate tracking on consent, document your legal basis, and avoid sending PII. If you hash identifiers, document and stabilize the hashing method.

  • Disable or deduplicate client-side events. If the browser gtag already sends purchase, server-side tracking for the same transaction can double-count. Choose one source per event or deduplicate by transaction_id.

  • Request metadata may not be automatic. In Laravel, pass user_agent, IP override, and session context explicitly if needed. The bundle’s Symfony request-based collection — including UA filtering — will not automatically work in a Laravel queue worker; wire the UA through your job payload yourself.

  • Configuration keys are bundle-specific. Do not guess the config prefix. Read DependencyInjection/Configuration.php; values include measurement ID, API secret, HTTP client settings, and now user-agent filter options.

  • Upgrade path from 0.3.8 to 0.3.9 is low risk. Two additive PRs, no documented breaking changes. Republish/merge your bundle config if you copied it, since new filter keys will not appear in an already-published config file.

  • Extension points to prefer. If you keep it in a Symfony service, decorate the tracker/client rather than editing vendor code. In Laravel, hide it behind your own interface and replace the implementation later.

  • Start with one event. Validate purchase or generate_lead end-to-end in DebugView before instrumenting the whole funnel. Server-side analytics failures are quiet; incremental rollout is safer.

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.
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings
coleus/apps
seampact/laravel
emmyconceptng/laravel-ng-postcode
jjsquady/round-robin
skyraptor/chatter
edlugz/sasapay
abbasudo/artisan-route