Build A Complete Support Ticket System In Laravel Filament

image
image
image
image
image
image
image
image
Build a Complete Support Ticket System in Laravel Filament

Build a Complete Support Ticket System in Laravel Filament

Managing support queries efficiently is crucial for any SaaS or product-based application. If you’re building an internal tool, customer portal, or admin dashboard, a support ticket system is often a necessary feature. In this comprehensive tutorial, we’ll walk through how to build a Support Ticket System in Laravel using Filament — a powerful and elegant admin panel built on top of Laravel.

By the end of this guide, you’ll have a full-featured ticket management system with support for priorities, statuses, admin assignment, and threaded replies.

✅ Why Laravel Filament?

Before diving in, here’s why Filament is a perfect choice:

  1. 🔹 Fully reactive and Tailwind-based admin UI
  2. 🔹 Fast setup with pre-built table, form, and relation managers
  3. 🔹 Highly customizable and developer-friendly
  4. 🔹 Ideal for internal tools and custom CMS interfaces

🧱 Step 1: Create Models and Migrations

We’ll need two models:

  1. Ticket – represents the main support query.
  2. TicketReply – stores replies to a ticket.

🎯 Run Artisan Commands

Open your terminal and run:

php artisan make:model Ticket -m
php artisan make:model TicketReply -m

This will generate the models and their respective migrations.

🧾 Ticket Model

// app/Models/Ticket.php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Ticket extends Model
{
protected $guarded = [];

public function user()
{
return $this->belongsTo(User::class);
}

public function assignedAdmin()
{
return $this->belongsTo(User::class, 'assigned_admin_id');
}

public function replies()
{
return $this->hasMany(TicketReply::class);
}
}

🧾 TicketReply Model

// app/Models/TicketReply.php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class TicketReply extends Model
{
protected $guarded = [];

public function ticket()
{
return $this->belongsTo(Ticket::class);
}

public function user()
{
return $this->belongsTo(User::class);
}
}

🧱 Migrations

create_tickets_table.php

Schema::create('tickets', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->constrained()->onDelete('cascade');
$table->foreignId('assigned_admin_id')->nullable()->constrained('users')->nullOnDelete();
$table->string('title');
$table->text('description');
$table->enum('status', ['open', 'in_progress', 'closed'])->default('open');
$table->enum('priority', ['low', 'medium', 'high'])->default('medium');
$table->timestamps();
});

create_ticket_replies_table.php

Schema::create('ticket_replies', function (Blueprint $table) {
$table->id();
$table->foreignId('ticket_id')->constrained()->onDelete('cascade');
$table->foreignId('user_id')->constrained()->onDelete('cascade');
$table->text('message');
$table->string('attachment')->nullable();
$table->timestamps();
});

Run the migrations:

php artisan migrate

🧩 Step 2: Generate the Filament Resource

Use the following command to scaffold the resource:

php artisan make:filament-resource Ticket

This command will generate:

  1. TicketResource.php
  2. Pages for listing, creating, editing
  3. Stub for relation managers

🎨 Step 3: Configure Ticket Resource

Update TicketResource.php to include:

Form Schema

public static function form(Form $form): Form
{
return $form->schema([
TextInput::make('title')->required(),
Textarea::make('description')->required()->rows(5),
Select::make('priority')
->options([
'low' => 'Low',
'medium' => 'Medium',
'high' => 'High',
])
->default('medium')->required(),
Select::make('status')
->options([
'open' => 'Open',
'in_progress' => 'In Progress',
'closed' => 'Closed',
])
->default('open')->required(),
Select::make('assigned_admin_id')
->label('Assigned To')
->relationship('assignedAdmin', 'name')
->searchable()
->preload()
->nullable(),
]);
}

Table Columns and Reply Action

public static function table(Table $table): Table
{
return $table
->columns([
TextColumn::make('title')->searchable()->sortable(),
TextColumn::make('priority')->badge(),
TextColumn::make('status')->badge(),
TextColumn::make('assignedAdmin.name')->label('Assigned To'),
TextColumn::make('created_at')->dateTime(),
])
->actions([
EditAction::make(),
Action::make('reply')
->label('Reply')
->icon('heroicon-o-chat-bubble-left-right')
->form([
Textarea::make('message')->label('Your Reply')->required()->rows(4),
])
->action(function (array $data, Ticket $record): void {
$record->replies()->create([
'message' => $data['message'],
'user_id' => auth()->id(),
]);
})
->modalHeading('Reply to Ticket')
->modalSubmitActionLabel('Send Reply'),
])
->bulkActions([
DeleteBulkAction::make(),
]);
}

➕ Step 4: Assign User on Ticket Creation

Update the CreateTicket page:

// app/Filament/Resources/TicketResource/Pages/CreateTicket.php

class CreateTicket extends CreateRecord
{
protected static string $resource = TicketResource::class;

protected function mutateFormDataBeforeCreate(array $data): array
{
$data['user_id'] = auth()->id();
return $data;
}
}

This ensures the logged-in user is automatically assigned as the ticket creator.

🔁 Step 5: Create RepliesRelationManager.php at the below path

app/Filament/Resources/TicketResource/RelationManagers/RepliesRelationManager.php

Then add the below code to it:

namespace App\Filament\Resources\TicketResource\RelationManagers;

use Filament\Forms;
use Filament\Forms\Form;
use Filament\Resources\RelationManagers\RelationManager;
use Filament\Tables;
use Filament\Tables\Table;

class RepliesRelationManager extends RelationManager
{
protected static string $relationship = 'replies';

public function form(Form $form): Form
{
return $form->schema([
Forms\Components\Textarea::make('message')
->required()
->label('Reply Message'),
Forms\Components\FileUpload::make('attachment')
->directory('ticket-attachments')
->label('Attachment')
->nullable(),
]);
}

public function table(Table $table): Table
{
return $table
->columns([
Tables\Columns\TextColumn::make('message')->wrap()->limit(100),
Tables\Columns\TextColumn::make('created_at')->dateTime()->label('Replied At'),
])
->filters([])
->headerActions([
Tables\Actions\CreateAction::make()
->mutateFormDataUsing(function (array $data): array {
$data['user_id'] = auth()->id(); // Automatically assign current user to reply
return $data;
}),
])
->actions([
Tables\Actions\EditAction::make(),
Tables\Actions\DeleteAction::make(),
]);
}
}

This will allow users to post replies and attach files via the relation manager.

🔁 Step 6: Go to TicketResource.php and add the below function in that file

public static function getRelations(): array
{
return [
\App\Filament\Resources\TicketResource\RelationManagers\RepliesRelationManager::class,
];
}


📖 Summary

✅ In this tutorial, we built a Laravel Filament Support Ticket System with the following features:

  1. Ticket creation and listing
  2. Priority and status labels
  3. Admin assignment of tickets
  4. Inline ticket replies with modal forms
  5. Threaded replies with attachments via the relation manager

🚀 GitHub Repository

Want to explore the complete source code? Clone or star the GitHub repo below:

🔗 GitHub: https://github.com/codehunger-team/support-ticket-system-using-laravel-filament

Feel free to fork the repo, customize it, and contribute by submitting pull requests. Star ⭐ the project if it helped you!

💼 About CodeHunger Pvt Ltd

This project is proudly created by CodeHunger Pvt Ltd, a Laravel and custom software development company focused on building practical and powerful web solutions.

  1. 🌐 Website: https://www.codehunger.in
  2. 📧 Contact Us: https://www.codehunger.in/contact-us
  3. 🧠 Blog: https://www.codehunger.in/blog
  4. 💬 LinkedIn: https://www.linkedin.com/company/104989660/admin/dashboard


Below I have added the screenshots of the code, how it will looks when you create one ticket system for you.


Edit and Reply Part