eg-mohamed / laravel-ga4-events
A Laravel package to push events to Google Tag Manager dataLayer from PHP, Livewire, and JavaScript.
Fund package maintenance!
Requires
- php: ^8.3
- illuminate/support: ^12.0||^13.0
- spatie/laravel-package-tools: ^1.93
Requires (Dev)
- laravel/pint: ^1.29
- nunomaduro/collision: ^8.9.1
- orchestra/testbench: ^8.0||^9.0||^10.0||^11.0
- pestphp/pest: ^4.4.3
- pestphp/pest-plugin-arch: ^4.0
- pestphp/pest-plugin-laravel: ^4.1
README
Push events to Google Tag Manager's dataLayer from Laravel, Livewire, and plain JavaScript through one unified bridge. The package injects the GTM snippet, validates payloads, and provides browser-side debug output through console.log.
Features
- Push events to
window.dataLayerfor GTM to handle - Auto-injects the GTM container snippet
- Client-side payload validation before pushing
- Works from JavaScript, DOM custom events, and Livewire
- Blade component and directive for easy layout injection
- Console debug output
Compatibility
- PHP:
^8.3 - Laravel:
12.x,13.x
Installation
composer require eg-mohamed/laravel-ga4-events
Publish the config:
php artisan vendor:publish --tag="ga4-events-config"
Quick Start
- Set your GTM container ID in
.env:
GTM_CONTAINER_ID=GTM-XXXXXXX GTM_EVENTS_ENABLED=true
- Inject the bridge once in your layout (before
</body>):
<x-ga4-events />
Legacy directive also works:
@ga4Events
- Push events from JavaScript:
window.GTMEvents.track('purchase_started', { currency: 'USD', value: 99.95, item_count: 2, })
GTM receives {event: 'purchase_started', currency: 'USD', value: 99.95, item_count: 2} in dataLayer.
Livewire Usage
$this->dispatch('gtm-event', name: 'profile_updated', params: ['section' => 'security']);
The bridge normalizes wrapped Livewire payload shapes automatically.
JavaScript API
track(name, params = {})
window.GTMEvents.track('add_to_cart', { product_id: 15, value: 150 })
dispatch(payload, source = 'manual')
window.GTMEvents.dispatch({ name: 'search', params: { term: 'sneakers' } })
DOM custom event
window.dispatchEvent(new CustomEvent('gtm:event', { detail: { name: 'checkout_step', params: { step: 2 } }, }))
config
console.log(window.GTMEvents.config)
Payload Structure
{
"name": "event_name",
"params": {
"key": "value"
}
}
Validation rules:
namemust be a non-empty string matchingallowed_name_patternnamemust not exceedmax_event_name_lengthparamsmust be an object- param keys validated for length
- param values support
string,number,boolean,null, or nested objects - string values truncated to
max_param_value_length
Configuration
Published at config/ga4-events.php:
return [ 'enabled' => (bool) env('GTM_EVENTS_ENABLED', true), 'container_id' => env('GTM_CONTAINER_ID'), 'inject_gtm_script' => (bool) env('GTM_EVENTS_INJECT_SCRIPT', true), 'event_bus_name' => env('GTM_EVENTS_EVENT_BUS_NAME', 'gtm:event'), 'livewire_event_name' => env('GTM_EVENTS_LIVEWIRE_EVENT_NAME', 'gtm-event'), 'global_js_object' => env('GTM_EVENTS_GLOBAL_JS_OBJECT', 'GTMEvents'), 'debug' => (bool) env('GTM_EVENTS_DEBUG', false), 'strict_validation' => (bool) env('GTM_EVENTS_STRICT_VALIDATION', false), 'drop_invalid_events' => (bool) env('GTM_EVENTS_DROP_INVALID_EVENTS', true), 'max_event_name_length' => (int) env('GTM_EVENTS_MAX_EVENT_NAME_LENGTH', 40), 'max_params' => (int) env('GTM_EVENTS_MAX_PARAMS', 25), 'max_param_key_length' => (int) env('GTM_EVENTS_MAX_PARAM_KEY_LENGTH', 40), 'max_param_value_length' => (int) env('GTM_EVENTS_MAX_PARAM_VALUE_LENGTH', 100), 'max_param_nesting' => (int) env('GTM_EVENTS_MAX_PARAM_NESTING', 4), 'allowed_name_pattern' => env('GTM_EVENTS_ALLOWED_NAME_PATTERN', '/^[a-zA-Z][a-zA-Z0-9_]*$/'), 'console_prefix' => env('GTM_EVENTS_CONSOLE_PREFIX', '[GTM Events]'), ];
Debug Mode
GTM_EVENTS_DEBUG=true
Example console output:
[GTM Events] [INFO] GTM bridge initialized.
[GTM Events] [INFO] Event pushed to dataLayer from api.
[GTM Events] [ERROR] Invalid GTM payload from livewire.
Artisan Command
php artisan gtm-events:check
Validates that the package is enabled and the container ID is set.
Skip Auto Injection
If you load the GTM snippet yourself:
GTM_EVENTS_INJECT_SCRIPT=false
The bridge still pushes events to window.dataLayer.
PHP-side Validation
use MohamedSaid\LaravelGa4Events\Facades\LaravelGa4Events; $result = LaravelGa4Events::track('purchase_completed', ['value' => 120]); // $result['valid'] — bool // $result['errors'] — array of strings // $result['name'] — sanitized name // $result['params'] — sanitized params
Changelog
Please see CHANGELOG for information about changes.
Contributing
Please see CONTRIBUTING for contribution details.
Security
Please review our security policy for reporting vulnerabilities.
Credits
License
The MIT License (MIT). Please see License File for more information.