electrictomcat/laravel-google-ads-conversions

Drop-in offline conversion tracking for Laravel apps using Google Ads.

Maintainers

Package info

github.com/electrictomcat/laravel-google-ads-conversions

pkg:composer/electrictomcat/laravel-google-ads-conversions

Fund package maintenance!

electrictomcat

Statistics

Installs: 4

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 1

v0.1.0 2026-05-03 19:04 UTC

This package is auto-updated.

Last update: 2026-05-03 19:15:31 UTC


README

Latest Version on Packagist GitHub Tests Action Status GitHub Code Style Action Status Total Downloads

Drop-in offline conversion tracking for Laravel apps using Google Ads.

  • Captures gclid (and gbraid/wbraid) from incoming traffic and persists it across the visitor's session
  • Records conversion events with a one-line API
  • Buffers in cache, syncs to your database, and uploads to Google Ads in batched, queued jobs
  • Honors Google Ads' minimum reporting delay (default 6 hours)
  • Bring-your-own model — implement a small contract or use the included Lead model out of the box
  • Supports both call-site values (record('Event', 100)) and config-mapped defaults

Status: pre-release. Not yet on Packagist.

Installation

composer require electrictomcat/laravel-google-ads-conversions

Publish the config and the migration:

php artisan vendor:publish --tag="laravel-google-ads-conversions-config"
php artisan vendor:publish --tag="laravel-google-ads-conversions-migrations"
php artisan migrate

Add these to your .env (see Google's OAuth setup for how to mint the refresh token):

GOOGLE_ADS_DEVELOPER_TOKEN=
GOOGLE_ADS_CLIENT_ID=
GOOGLE_ADS_CLIENT_SECRET=
GOOGLE_ADS_REFRESH_TOKEN=
GOOGLE_ADS_CUSTOMER_ID=123-456-7890

Register the middleware in bootstrap/app.php:

->withMiddleware(function (Middleware $middleware) {
    $middleware->web(append: [
        \ElectricTomCat\GoogleAdsConversions\Http\Middleware\CaptureGclid::class,
    ]);
})

Schedule the upload job in routes/console.php:

use ElectricTomCat\GoogleAdsConversions\Jobs\UploadPendingConversions;
use Illuminate\Support\Facades\Schedule;

Schedule::job(new UploadPendingConversions)->hourly();

Usage

Recording a conversion

From anywhere in your app — controllers, jobs, Livewire components, observers:

use ElectricTomCat\GoogleAdsConversions\Facades\GoogleAdsConversions;

GoogleAdsConversions::record('Quote Form', 100);

The first argument is your internal event name. The second is an optional value. The full signature is:

GoogleAdsConversions::record(
    eventName: 'Quote Form',
    value: 100.0,        // optional — falls back to per-event config
    currency: 'USD',     // optional — falls back to per-event then package default
    gclid: null,         // optional — manually override the GCLID lookup
);

Mapping events to Google Ads conversion actions

Edit config/google-ads-conversions.php. Each event entry is either a string (just the action name) or an array with optional value/currency defaults:

'events' => [

    // Simple: event name → Google Ads action name (or full resource path)
    'Quote Form' => 'Quote Submission',
    'Phone Call' => 'customers/1234567890/conversionActions/111111',

    // With per-event default value/currency that the call site can still override
    'Demo Booked' => [
        'action'   => 'Demo Booked',
        'value'    => 250.00,
        'currency' => 'USD',
    ],

    // Catches any event named "Page Navigation: /anything" by prefix
    'Page Navigation' => 'Page Navigation',

],

The call site always wins — record('Demo Booked', 999) overrides the config's 250.00. Omit the value at the call site to use the config default.

Bring your own model

The package ships a Lead model and matching migration that work out of the box. If you'd rather use your own model — say, you already have a Visitor table and want to track conversions there — implement the HasConversions contract.

The fastest path: drop the HasConversionsTrait onto your existing model:

use ElectricTomCat\GoogleAdsConversions\Contracts\HasConversions;
use ElectricTomCat\GoogleAdsConversions\Models\Concerns\HasConversionsTrait;
use Illuminate\Database\Eloquent\Casts\AsCollection;

class Visitor extends Model implements HasConversions
{
    use HasConversionsTrait;

    protected $fillable = ['gclid', 'visitor_id', 'conversions', /* ... */];

    protected $casts = [
        'conversions' => AsCollection::class,
    ];
}

Make sure your table has at minimum these columns:

  • gclid (string, unique, indexed)
  • visitor_id (uuid, nullable)
  • conversions (json, nullable)

Then point the package at it:

// config/google-ads-conversions.php
'model' => \App\Models\Visitor::class,

For full control, implement HasConversions from scratch — see src/Contracts/HasConversions.php for the contract.

How the pipeline works

  1. Middleware (CaptureGclid) — runs on the landing request, extracts gclid from the URL, sets cookies + session, buffers a stub lead record in cache.
  2. Recording (GoogleAdsConversions::record()) — pushes a conversion entry into a per-gclid cache bucket. Cheap. Fire from anywhere, including HTTP requests where the user has no gclid on the URL but does have one in their session/cookie.
  3. Sync (syncToDatabase()) — flushes the cache buffer into the configured model's table. Runs as the first half of the queued job.
  4. Upload (uploadPendingConversions()) — finds every pending conversion older than the delay window and ships eligible batches to Google Ads via UploadClickConversions. Marks each shipped conversion as 'uploaded' with a timestamp.

Testing

composer test

The suite uses Pest 4 + Orchestra Testbench against an in-memory SQLite database.

Changelog

See CHANGELOG.md.

Contributing

Issues and pull requests welcome.

Credits

License

The MIT License (MIT). See LICENSE.md.