cavitznky/ticket-system
cavitznky/ticket-system is a Laravel package for adding support-ticket and helpdesk workflows to your application. It provides a foundation for creating, tracking, and managing user-submitted issues with familiar Eloquent models and PHP conventions, making ticketing easier to integrate into existing...
Install
composer require cavitznky/ticket-system
Because this is a young package (single release, 2025‑03‑27), treat the first install as an investigation step rather than a copy‑paste setup. Confirm the package registers its service provider via auto‑discovery:
composer show cavitznky/ticket-system
php artisan about | grep -i ticket
Publish and migrate
php artisan vendor:publish --provider="Cavitznky\TicketSystem\TicketSystemServiceProvider"
php artisan migrate
If the provider name differs (check vendor/cavitznky/ticket-system/composer.json → extra.laravel.providers), list tags instead:
php artisan vendor:publish
Publish selectively: config, migrations, and views are separate tags in most Laravel packages, and you rarely want to publish views on day one.
Where to look first
vendor/cavitznky/ticket-system/src/ — models, migrations, Livewire components.vendor/cavitznky/ticket-system/config/ — the ticket status list, table names, auth model.vendor/cavitznky/ticket-system/routes/ — whether routes are web, panel‑scoped, or not registered at all.vendor/cavitznky/ticket-system/resources/views/ — Blade/Livewire markup you'll likely override.First use case
The intended day‑one win is: a user submits a support request from a form, the request is stored as a ticket with an open status, and an admin/agent sees a list they can update. Get that loop working before touching statuses or notifications.
A minimal smoke test is:
use Cavitznky\TicketSystem\Models\Ticket;
$ticket = Ticket::create([
'user_id' => auth()->id(),
'subject' => 'Cannot export invoices',
'body' => 'The export button returns a 500.',
]);
dump($ticket->status); // confirms your default status wiring
If that fails, the issue is almost always unrun migrations, a mismatched user_id column, or a missing status enum/default — not the package itself.
Attach tickets to your existing user model
Most ticket packages ship a trait or relationship expectation on the user side. Wire it up explicitly:
// app/Models/User.php
use Cavitznky\TicketSystem\Concerns\HasTickets; // verify the trait name
class User extends Authenticatable
{
use HasTickets;
}
If no trait is shipped, add a plain relationship:
public function tickets()
{
return $this->hasMany(Ticket::class);
}
Embed the Livewire components rather than building controllers
Since the package is Livewire‑first, the fast path is to render its components inside your own layout, keeping your auth/theme and letting the package own the CRUD:
{{-- resources/views/support/index.blade.php --}}
<livewire:ticket-system.ticket-list />
Discover the exact component aliases from the service provider's Livewire::component(...) calls — do not guess the alias from the docs, since naming conventions vary between ticket-system.ticket-list and ticket-list.
Separate the "create" surface from the "list" surface
A pattern that scales well:
/support/new page rendering the creation component, gated by auth./support page rendering the list, scoped to auth()->id() for customers./admin/tickets page in your existing admin panel (Filament, Nova, or a second Livewire component) listing all tickets.This keeps customer and agent UX independent while reusing the package's core models.
Status transitions belong in your domain, not your Blade
Treat status as an enum and route every change through one method, so you can add audit logs, notifications, and SLA logic later without hunting through views:
class TicketWorkflow
{
public function transition(Ticket $ticket, TicketStatus $to, ?User $by = null): void
{
$from = $ticket->status;
// Guard invalid jumps here (e.g. resolved -> open requires a new ticket)
$ticket->update([
'status' => $to,
'resolved_at' => $to === TicketStatus::Resolved ? now() : null,
]);
event(new TicketStatusChanged($ticket, $from, $to, $by));
}
}
Then wrap the package's update action (or replace it) so the Livewire component calls transition() instead of assigning status directly.
Notifications via events, not model observers
Listen to your status event and fan out — email to the ticket owner on reply/resolve, Slack/Slack‑style webhook to the agent channel on open. Keeping this out of the package's models means a future package upgrade won't silently drop your notifications.
Event::listen(TicketStatusChanged::class, function ($event) {
if ($event->to === TicketStatus::Resolved) {
Notification::send($event->ticket->user, new TicketResolved($event->ticket));
}
});
Route integration for non‑Livewire surfaces
If you need an API or an Inertia front end, don't fight the Livewire components — instantiate the models and reuse the migration schema. Keep the package's table names (likely tickets, ticket_replies/ticket_messages) and add your own controllers on top; you can then remove the package's web routes from a service provider if they clash.
Testing pattern
Livewire components test cheaply, which is the main reason to adopt this package rather than hand‑rolling:
Livewire::actingAs($user)
->test('ticket-system.ticket-create')
->set('subject', 'Refund not received')
->set('body', 'Order #1234')
->call('submit')
->assertHasNoErrors();
$this->assertDatabaseHas('tickets', ['subject' => 'Refund not received']);
This gives you a regression net around the package's forms without touching its internals.
Livewire version compatibility is the top risk. A package released in early 2025 may target Livewire v3 exclusively. Check composer.json for livewire/livewire constraints before installing into a Livewire v2 app; a silent major‑version mismatch produces confusing component errors rather than a clean dependency failure.
Verify the namespace before writing imports. With 0 stars and a single release, blog posts and AI‑generated snippets will invent class names. Grep the vendor directory once:
grep -r "namespace" vendor/cavitznky/ticket-system/src | head -20
Config caching bites during setup. After changing published config, run php artisan config:clear. In production, remember config:cache — packages that read config at boot time inside a closure will break if the config key is missing.
Overriding views is version‑fragile. Published views are a fork, not an override. If you must restyle, prefer Tailwind classes passed into the component or a thin wrapper Blade file; you'll still get package upgrades for logic. Note the package's Tailwind classes may not be in your content globs — add ./vendor/cavitznky/ticket-system/resources/views/**/*.blade.php to tailwind.config.js content, or the components render unstyled.
Auth guard assumptions. Check whether the package resolves the acting user via auth()->user() or a configurable auth.guard. In multi‑guard apps (admin + customer), a hard‑coded default guard means agent actions run as the wrong user. Fix by setting the guard in config or wrapping the components in your own authenticated route group.
Authorization is your job. A ticket list component that queries Ticket::all() will leak other users' tickets. Either use the package's scoping (if it has a scopeOwnedBy) or override the component's query in a subclass. Add a TicketPolicy with view, update, and reply methods and enforce it in your wrapper routes — do not assume the package ships one.
Migration collisions. If you already have a tickets table (customers frequently do), publishing migrations will either fail or need renaming. Grep your database/migrations first, and if there's a conflict, publish and edit the package migration's Schema::create table name plus the config's table key together — they must match.
Status values are stringly typed until you make them an enum. If the package stores status as a plain string, add a cast in your own Ticket subclass:
protected $casts = ['status' => TicketStatus::class];
Be careful: a cast on a package model requires subclassing or a castUsing config hook. If neither is supported, leave it a string and compare against constants.
Extension points to look for, in priority order: config file → route registration → model $fillable/$casts → Livewire component subclass → published views. Prefer changing config over forking models; every published file increases your upgrade tax on an unmaintained package.
Debugging a blank component. If a Livewire component renders nothing, in order: check php artisan route:list for a mounting route, check the browser console for a 404 on /livewire/update, then confirm assets are published and @livewireScripts is in your layout. Missing Livewire scripts is the most common "it works in tests but not in the browser" cause.
Maintenance posture. With zero stars and one release, don't put this on a critical path without pinning the version ("cavitznky/ticket-system": "1.0.*") and writing your own feature tests around the flows you depend on. Budget for a future migration to a maintained alternative, and keep your ticket domain logic in your own service/event layer so that migration is a data port, not a rewrite.
How can I help you explore Laravel packages today?