selli/ticketing

The agnostic, headless, multi-tenant ticketing domain engine for Laravel. Attach tickets to anything — your models, your UI, your tenant.

Maintainers

Package info

github.com/Sellinnate/laravel-ticket-engine

Homepage

pkg:composer/selli/ticketing

Fund package maintenance!

Sellinnate

Statistics

Installs: 0

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

dev-main 2026-06-29 07:09 UTC

This package is auto-updated.

Last update: 2026-06-29 11:28:57 UTC


README

Latest Version on Packagist GitHub Tests Action Status Total Downloads

The agnostic, headless, multi-tenant ticketing domain engine for Laravel. Attach tickets to anything — your models, your UI, your tenant.

selli/ticketing is not a help-desk app and not a UI. It is the reusable domain layer you build any ticketing mechanism on — customer support, technical interventions, internal requests, incident management, work queues — in days instead of weeks. A ticket can be about any Eloquent model in your app (an order, a device, a contract), about a service, or about nothing at all. The link to your domain happens by contract (Ticketable), never by coupling to your schema.

Headless by design: zero UI, zero forced user model, multi-tenant by default, workflow configurable per ticket type. The same engine serves a Filament back-office, an Inertia/Vue front-end, a mobile API or a queued job without changes.

📖 Full documentation: laravel-ticketing.selli.io

Features

  • Config-driven workflow per ticket type, with guards and a Spatie state-class bridge — validated at boot.
  • SLA & escalation — first-response / next-response / resolution targets on business hours, with a pausing clock.
  • Routing & assignment — teams, pluggable strategies (round-robin, least-busy, skill-based, custom) and data-driven routing rules.
  • Collaboration — attachments (signed URLs), @mentions, canned responses, macros, merge/split, tags.
  • CSAT with stateless, signed rating links.
  • Automation — a data-driven trigger→conditions→actions rule engine, plus SSRF-guarded outbound webhooks.
  • Notifications — mail, database (in-app bell), broadcast and Slack, with per-user/per-tenant preferences.
  • Channels — an opt-in versioned REST API, email-to-ticket (threading, anti-loop, idempotent), and realtime broadcasting over Reverb.
  • Security — agnostic authorization policies, an immutable audit trail, and GDPR anonymisation / export / retention.

Installation

composer require selli/ticketing
php artisan ticketing:install   # publishes config + migrations, offers to migrate
php artisan ticketing:demo      # seeds a working example ticket

Quick start

Make any model the subject of tickets:

use Selli\Ticketing\Concerns\HasTickets;
use Selli\Ticketing\Contracts\Ticketable;

class Order extends Model implements Ticketable
{
    use HasTickets; // tickets() relation + openTicket() helper
}

Open and work tickets through the facade or the host helper:

use Selli\Ticketing\Facades\Ticketing;
use Selli\Ticketing\Enums\Priority;
use Selli\Ticketing\Enums\MessageVisibility;

// A free-standing request
$ticket = Ticketing::open(
    type: 'support',
    title: 'Login is broken',
    requester: $user,
    priority: Priority::High,
);

// A ticket about any host entity
$ticket = $order->openTicket(type: 'incident', title: 'Missing shipment', requester: $user);

// Conversation, with public/internal visibility
Ticketing::for($ticket)->postMessage($agent, 'Looking into it', MessageVisibility::Internal);
Ticketing::for($ticket)->postMessage($agent, 'Refund issued', MessageVisibility::Public);

Actors are agnostic too — implement CanRequestTickets and/or CanActOnTickets on whichever model(s) represent your requesters and agents (even two different populations such as Customer and Operator).

Multi-tenancy

Tenancy is structural, not an afterthought. Every table carries a tenant column, every read is scoped by a global scope, every write is auto-assigned to the current tenant. With no tenant resolved the scope fails closed — only shared (null-tenant) rows are visible, never another tenant's data.

use Selli\Ticketing\Tenancy\TenantContext;

// CLI / queues / email: act explicitly as a tenant
app(TenantContext::class)->forTenant($tenantId, function () {
    Ticketing::open(type: 'support', title: '...', requester: $requester);
});

Bind your own TenantResolver (or a bridge to stancl/spatie tenancy) to inherit the current tenant from your existing infrastructure.

Architecture at a glance

  • Three-level APITicketing facade for common cases, Action classes for fine control and testing, domain events for extension.
  • Everything is an eventTicketOpened, MessagePosted, … are the primary extension hooks. Automations, notifications, audit and integrations attach here.
  • Override-friendly — every model is resolved from the container and replaceable: Ticketing::useTicketModel(MyTicket::class).
  • Immutable audit trailticket_activities is append-only; updates/deletes throw.
  • Config-validated at boot — a workflow that references a missing state fails fast, not at runtime.

Testing

composer test            # Pest
composer test-coverage   # Pest with a 90% minimum
composer analyse         # PHPStan (Larastan) level 6
composer format          # Laravel Pint

Changelog

Please see CHANGELOG for more information on what has changed recently.

Security Vulnerabilities

Please review our security policy on how to report security vulnerabilities.

Credits

License

The MIT License (MIT). Please see License File for more information.