pdphilip/omnievent

OmniEvent for Laravel is a Laravel Model event tracking and searching with Elasticsearch module

Maintainers

Package info

gitlab.com/pdphilip/omnievent

Homepage

Issues

pkg:composer/pdphilip/omnievent

Statistics

Installs: 21 744

Dependents: 0

Suggesters: 0

Stars: 0

v3.2.0 2026-04-06 17:02 UTC

This package is auto-updated.

Last update: 2026-04-06 15:11:14 UTC


README

# OmniEvent for Laravel OmniEvent for Laravel

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

Track model events in Elasticsearch. Query them with Eloquent.

$user->triggerEvent('login', ['ip' => $request->ip()]);
$user->triggerEvent('purchase', ['amount' => 99.95, 'currency' => 'USD']);
$user->triggerEvent('password_reset');

Every event is stored as a document in Elasticsearch with a timestamp, optional metadata, and automatic request context (IP, browser, device, geolocation) via CfRequest.

Query events using the full Elasticsearch Eloquent builder - the same API you already know from Laravel-Elasticsearch:

// Quick search - returns Collection
$logins = User::eventSearch('login');

// Full power - Elasticsearch Eloquent builder
User::viaEvents()
    ->where('event', 'purchase')
    ->where('meta.amount', '>=', 50)
    ->where('created_at', '>=', now()->subDays(30))
    ->orderByDesc('created_at')
    ->paginate(20);

Events are automatically cleaned up when a model is deleted.

Requirements

Version
PHP8.2+
Laravel10 / 11 / 12
Elasticsearch8.x

Installation

composer require pdphilip/omnievent
php artisan omnievent:install

This publishes config/omnievent.php.

Requires a Laravel-Elasticsearch connection. See configuration guide.

Quick Start

1. Generate an event model

php artisan omnievent:make User

This creates app/Models/Events/UserEvent.php:

namespace App\Models\Events;

use App\Models\User;
use PDPhilip\OmniEvent\EventModel;

class UserEvent extends EventModel
{
    protected $baseModel = User::class;

    public function modelType(User $model): ?string
    {
        return null;
    }
}

2. Add the trait

use PDPhilip\OmniEvent\Eventable;

class User extends Model
{
    use Eventable;
}

That's it. The Elasticsearch index is created automatically on first boot.

3. Track events

$user->triggerEvent('login');
$user->triggerEvent('purchase', ['amount' => 49.99, 'currency' => 'EUR']);

Querying Events

Quick Search

Returns a Collection of event records matching the event name:

$logins = User::eventSearch('login');

Full Builder

Returns the Elasticsearch Eloquent builder for the event model. Chain any query method you'd use on a normal Eloquent model:

// Filter by event type and date range
User::viaEvents()
    ->where('event', 'purchase')
    ->where('created_at', '>=', now()->subMonth())
    ->get();

// Paginate
User::viaEvents()
    ->where('event', 'login')
    ->orderByDesc('created_at')
    ->paginate(15);

// Count
User::viaEvents()
    ->where('event', 'purchase')
    ->count();

// Filter by metadata
User::viaEvents()
    ->where('meta.currency', 'USD')
    ->where('meta.amount', '>=', 100)
    ->get();

// Filter by request context
User::viaEvents()
    ->where('request.country', 'US')
    ->where('request.deviceType', 'mobile')
    ->get();

Since viaEvents() returns a standard Elasticsearch Eloquent builder, you get access to everything: where, orderBy, limit, paginate, count, aggregate, searchTerm, distinct, and more. See the Laravel-Elasticsearch docs for the full query API.

Event Record Structure

Each event is stored as an Elasticsearch document with these fields:

FieldTypeDescription
model_idkeywordThe base model's primary key
model_typekeywordOptional type from modelType() method
eventkeywordThe event name
tsintegerUnix timestamp
metaflattenedCustom metadata (any key-value data)
created_atdatetimeLaravel timestamp

When save_request is enabled in config, each event also captures:

FieldTypeDescription
request.ipkeywordClient IP address
request.browserkeywordBrowser name and version
request.devicekeywordDevice brand and model
request.deviceTypekeyworddesktop, mobile, tablet, tv
request.oskeywordOperating system
request.countrykeywordISO country code
request.regionkeywordRegion/state
request.citykeywordCity
request.postal_codekeywordPostal code
request.latfloatLatitude
request.lonfloatLongitude
request.timezonekeywordTimezone
request.is_botbooleanBot detection flag
request.geogeo_pointGeoJSON point for geo queries

Request data is captured automatically via CfRequest. Behind Cloudflare, you get full geolocation. Without Cloudflare, you still get IP, browser, device, and bot detection.

Model Types

The modelType() method on your event model lets you tag events with a model subtype. Useful for segmenting events by user role, account tier, or any model attribute:

class UserEvent extends EventModel
{
    protected $baseModel = User::class;

    public function modelType(User $model): ?string
    {
        return $model->plan; // 'free', 'pro', 'enterprise'
    }
}

Then filter by type:

User::viaEvents()
    ->where('model_type', 'pro')
    ->where('event', 'export')
    ->count();

Return null to skip the type field entirely.

Resolving the Base Model

Every event record can resolve back to its source model:

$event = UserEvent::where('event', 'login')->first();

// Via relationship (eager-loadable)
$event->model;          // User instance
$event->model->name;    // 'David'

// Via direct lookup
$event->asModel();      // User instance or null

// Eager load across a collection
$events = UserEvent::where('event', 'login')
    ->with('model')
    ->get();

The base model is resolved from the $baseModel property on your event model. If not set, OmniEvent guesses it from the table name (user_events -> User).

Automatic Cleanup

When a model is deleted, all its events are automatically removed:

$user->delete(); // All UserEvent records for this user are deleted

This is registered via the Eventable trait's boot method. No manual cleanup needed.

Configuration

// config/omnievent.php
return [
    // Elasticsearch connection name
    'database' => 'elasticsearch',

    // Throw exceptions on event save failure (false = log and return false)
    'throw_exceptions' => true,

    // Capture request metadata (IP, browser, geo) with each event
    'save_request' => true,

    // Namespaces for model resolution
    'namespaces' => [
        'models' => 'App\Models',
        'events' => 'App\Models\Events',
    ],

    // File paths (relative to app/) for event model discovery
    'app_paths' => [
        'models' => 'Models/',
        'events' => 'Models/Events/',
    ],
];

Error Handling

By default, exceptions from Elasticsearch are thrown. Set throw_exceptions to false to silently log errors and return false from triggerEvent():

// config/omnievent.php
'throw_exceptions' => false,
// Now returns false instead of throwing
$success = $user->triggerEvent('login'); // false if ES is down

This is useful in production where event tracking should never break your application flow.

API Reference

Eventable Trait (on your base model)

MethodReturnsDescription
$model->triggerEvent(string $event, array $meta = [])boolSave an event with optional metadata
Model::viaEvents()BuilderElasticsearch Eloquent builder for the event model
Model::eventSearch(string $event)CollectionShorthand for viaEvents()->where('event', $event)->get()

EventModel (your event model class)

MethodReturnsDescription
$event->model()BelongsToRelationship to the base model (eager-loadable)
$event->asModel()?ModelDirect lookup of the base model
EventModel::saveEvent(Model $model, string $event, array $meta = [])boolCreate an event record
EventModel::deleteAllEvents(Model $model)voidDelete all events for a model
EventModel::validateSchema()arrayCreate the Elasticsearch index if it doesn't exist

Changelog

See CHANGELOG for recent changes.

Credits

License

The MIT License (MIT). See License File for details.