zedmagdy/filament-chat

A filament package for creating chat interfaces in your Laravel applications.
49 2
Install
composer require zedmagdy/filament-chat
Latest Version:v0.7.0
PHP:^8.3
License:MIT
Last Updated:Apr 6, 2026
Links: GitHub  ·  Packagist
Maintainer: ZED-Magdy

Filament Chat

Latest Version on Packagist GitHub Tests Action Status Total Downloads

A chat plugin for Filament v4 that supports configurable chat sources, one-to-one and group conversations, text and file attachments (via Spatie Media Library), read/unread tracking, search, and real-time updates via polling or broadcasting (Reverb/Pusher).

Requirements

  • PHP ^8.3
  • Laravel ^11.0 or ^12.0
  • Filament ^4.3.1 or ^5.0
  • Spatie Media Library (installed automatically)

Installation

Install via Composer:

composer require zedmagdy/filament-chat

Publish and run the migrations:

php artisan vendor:publish --tag="filament-chat-migrations"
php artisan migrate

If you haven't already, publish the Spatie Media Library migration as well:

php artisan vendor:publish --provider="Spatie\MediaLibrary\MediaLibraryServiceProvider" --tag="medialibrary-migrations"
php artisan migrate

Optionally publish the config:

php artisan vendor:publish --tag="filament-chat-config"

Optionally publish the views for customization:

php artisan vendor:publish --tag="filament-chat-views"

Full Integration Example

1. Add the HasChats trait to your User model

namespace App\Models;

use Illuminate\Foundation\Auth\User as Authenticatable;
use ZEDMagdy\FilamentChat\Traits\HasChats;

class User extends Authenticatable
{
    use HasChats;

    // ...
}

This gives your User model these relationships:

  • chatParticipations() - all chat participations
  • conversations() - all conversations the user is part of
  • sentMessages() - all messages sent by the user

2. Create a Chat Source

The quickest way is with the Artisan command:

# Interactive (prompts for name and model)
php artisan make:chat-source

# Non-interactive
php artisan make:chat-source Staff --model=User

This generates two files:

  • app/Chat/StaffChatSource.php — the chat source class
  • app/Filament/Pages/StaffChatPage.php — the Filament page

Then skip to step 4 to register it.

Manual setup

A chat source defines a category of chat (e.g. staff-to-staff, patient support). Create one class per source:

namespace App\Chat;

use App\Filament\Pages\StaffChatPage;
use App\Models\User;
use Illuminate\Database\Eloquent\Builder;
use ZEDMagdy\FilamentChat\ChatSource;

class StaffChatSource extends ChatSource
{
    public function getKey(): string
    {
        return 'staff';
    }

    public function getLabel(): string
    {
        return 'Staff Chat';
    }

    public function getIcon(): string
    {
        return 'heroicon-o-chat-bubble-left-right';
    }

    public function getParticipantModel(): string
    {
        return User::class;
    }

    public function getPageClass(): string
    {
        return StaffChatPage::class;
    }

    // Optional: filter which users can be added to new conversations
    public function getAvailableParticipantsQuery(): Builder
    {
        return User::query()->where('role', 'staff');
    }

    // Optional: allow users to create new conversations (default: true)
    public function allowsNewConversations(): bool
    {
        return true;
    }

    // Optional: enable group chats for this source (default: false)
    public function allowsGroupChats(): bool
    {
        return true;
    }

    // Optional: customize navigation
    public function getNavigationGroup(): ?string
    {
        return 'Communication';
    }

    public function getNavigationSort(): ?int
    {
        return 1;
    }

    // Optional: customize how participant names are displayed
    public function getParticipantDisplayName(\Illuminate\Database\Eloquent\Model $participant): string
    {
        return $participant->name;
    }

    // Optional: provide avatar URLs
    public function getParticipantAvatarUrl(\Illuminate\Database\Eloquent\Model $participant): ?string
    {
        return $participant->avatar_url;
    }
}

3. Create a Chat Page

Each chat source needs a thin Filament page class:

namespace App\Filament\Pages;

use ZEDMagdy\FilamentChat\Pages\ChatSourcePage;

class StaffChatPage extends ChatSourcePage
{
    protected static string $chatSourceKey = 'staff';
}

That's it. The page inherits its navigation label, icon, group, sort, and slug from the chat source.

4. Register the Plugin in your Panel

namespace App\Providers\Filament;

use App\Chat\StaffChatSource;
use Filament\Panel;
use Filament\PanelProvider;
use ZEDMagdy\FilamentChat\FilamentChatPlugin;

class AdminPanelProvider extends PanelProvider
{
    public function panel(Panel $panel): Panel
    {
        return $panel
            ->default()
            ->id('admin')
            ->path('admin')
            // ... other config
            ->plugin(
                FilamentChatPlugin::make()
                    ->sources([
                        StaffChatSource::class,
                    ])
            );
    }
}

5. Multiple Chat Sources

You can register multiple sources for different chat contexts:

// app/Chat/PatientChatSource.php
namespace App\Chat;

use App\Filament\Pages\PatientChatPage;
use App\Models\Patient;
use ZEDMagdy\FilamentChat\ChatSource;

class PatientChatSource extends ChatSource
{
    public function getKey(): string
    {
        return 'patient';
    }

    public function getLabel(): string
    {
        return 'Patient Messages';
    }

    public function getIcon(): string
    {
        return 'heroicon-o-heart';
    }

    public function getParticipantModel(): string
    {
        return Patient::class; // any model with HasChats trait
    }

    public function getPageClass(): string
    {
        return PatientChatPage::class;
    }
}
// app/Filament/Pages/PatientChatPage.php
namespace App\Filament\Pages;

use ZEDMagdy\FilamentChat\Pages\ChatSourcePage;

class PatientChatPage extends ChatSourcePage
{
    protected static string $chatSourceKey = 'patient';
}

Register both in your panel:

->plugin(
    FilamentChatPlugin::make()
        ->sources([
            StaffChatSource::class,
            PatientChatSource::class,
        ])
)

6. Creating Conversations from the UI

Users can start new conversations by clicking the + button in the chat sidebar. This opens a modal where they select a participant (or multiple for group chats).

Behavior:

  • Direct chats: If a conversation already exists between the two users in the same source, it opens the existing one instead of creating a duplicate.
  • Group chats: Only available if allowsGroupChats() returns true. Users provide a group name and select multiple participants.
  • Participant list: Filtered by getAvailableParticipantsQuery() — the current user is excluded automatically.

Disabling conversation creation:

Override allowsNewConversations() in your chat source to hide the button:

public function allowsNewConversations(): bool
{
    return false; // Users can only see existing conversations
}

This is useful for system-managed chats where conversations are created programmatically (e.g. a support ticket system that auto-creates a chat per ticket).

7. Creating Conversations Programmatically

use ZEDMagdy\FilamentChat\Models\Conversation;
use ZEDMagdy\FilamentChat\Models\Participant;
use ZEDMagdy\FilamentChat\Models\Message;

// Create a direct conversation
$conversation = Conversation::create([
    'source' => 'staff',
    'type' => 'direct',
]);

// Add participants
Participant::create([
    'conversation_id' => $conversation->id,
    'participantable_id' => $user1->id,
    'participantable_type' => $user1->getMorphClass(),
]);

Participant::create([
    'conversation_id' => $conversation->id,
    'participantable_id' => $user2->id,
    'participantable_type' => $user2->getMorphClass(),
]);

// Send a message
$message = Message::create([
    'conversation_id' => $conversation->id,
    'senderable_id' => $user1->id,
    'senderable_type' => $user1->getMorphClass(),
    'body' => 'Hello!',
]);

// Create a group conversation
$group = Conversation::create([
    'source' => 'staff',
    'type' => 'group',
    'name' => 'Project Team',
]);

// Send a system message (no sender)
Message::create([
    'conversation_id' => $group->id,
    'body' => 'User1 created the group',
]);

8. Working with Attachments

The Message model uses Spatie Media Library. Attachments are stored in the chat-attachments media collection:

$message = Message::create([
    'conversation_id' => $conversation->id,
    'senderable_id' => $user->id,
    'senderable_type' => $user->getMorphClass(),
    'body' => 'Check out this file',
]);

// Add an attachment
$message->addMedia($pathToFile)
    ->toMediaCollection('chat-attachments');

// Get attachments
$message->getMedia('chat-attachments');

In the UI, the MessageInput Livewire component uses Filament's SpatieMediaLibraryFileUpload for seamless file uploads.

Real-time Updates

Polling (default)

Out of the box, the chat window polls for new messages. Configure the interval in your .env or config:

FILAMENT_CHAT_REALTIME_MODE=polling
// config/filament-chat.php
'realtime' => [
    'mode' => 'polling',
    'polling_interval' => '5s',
],

Broadcasting (Reverb / Pusher)

For real-time updates via WebSockets:

FILAMENT_CHAT_REALTIME_MODE=broadcasting

The package broadcasts MessageSent and MessagesRead events on private channels (chat.conversation.{id}). Channel authorization is handled automatically - only conversation participants can listen.

Make sure your Laravel broadcasting is configured (Reverb, Pusher, etc.) and that your frontend includes the Echo setup.

Configuration

// config/filament-chat.php
return [
    'table_prefix' => 'chat_',

    'realtime' => [
        'mode' => env('FILAMENT_CHAT_REALTIME_MODE', 'polling'),
        'polling_interval' => '5s',
    ],

    'attachments' => [
        'disk' => env('FILAMENT_CHAT_DISK', 'public'),
        'collection' => 'chat-attachments',
        'max_files' => 4,
        'max_file_size' => 10240, // KB
        'accepted_types' => [
            'image/jpeg', 'image/png', 'image/gif', 'image/webp',
            'application/pdf',
            'application/vnd.ms-excel',
            'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
            'text/csv',
        ],
    ],

    'messages_per_page' => 50,
    'conversations_per_page' => 25,

    'theme' => [
        'sent_bg' => 'primary-500',
        'received_bg' => 'gray-100',
        'sent_text' => 'white',
        'received_text' => 'gray-900',
    ],

    // Override with your own model classes
    'models' => [
        'conversation' => \ZEDMagdy\FilamentChat\Models\Conversation::class,
        'message' => \ZEDMagdy\FilamentChat\Models\Message::class,
        'participant' => \ZEDMagdy\FilamentChat\Models\Participant::class,
    ],
];

Custom Models

You can extend the built-in models and register them in the config:

namespace App\Models;

use ZEDMagdy\FilamentChat\Models\Conversation as BaseConversation;

class Conversation extends BaseConversation
{
    // Add your custom logic
}
// config/filament-chat.php
'models' => [
    'conversation' => \App\Models\Conversation::class,
],

Events

The package dispatches the following events:

Event Broadcasts Description
MessageSent Yes Fired when a message is sent. Broadcasts on chat.conversation.{id}.
ConversationCreated No Fired when a new conversation is created.
MessagesRead Yes Fired when a user reads messages. Broadcasts read receipts.

Listen to them in your EventServiceProvider or with Event::listen():

use ZEDMagdy\FilamentChat\Events\MessageSent;

Event::listen(MessageSent::class, function (MessageSent $event) {
    // Send a notification, update counters, etc.
    $event->message;
    $event->message->conversation;
    $event->message->senderable;
});

Testing

composer test

Changelog

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

Contributing

Please see CONTRIBUTING for details.

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.