Chat
CommunityTeam chat for your panel: direct messages and groups, @mentions, reactions, read receipts, a pinnable slide-over, and messages that link to your panel's records.
filament/
namespace. Review the source and install at your own risk. Found
malware or an unresolved security issue the author won't
address?
Report it
.
Author:
asign
Documentation
- Screenshots
- Features
- Requirements
- Installation
- Configuration
- Record references
- Real-time delivery
- Notifications
- Translations
- Customising
- Integration notes
- Troubleshooting
- AI agents
- Testing
- License
Team chat for Filament panels. Direct messages and groups, @mentions, editing, reactions, read receipts, a slide-over you can pin next to any page — and messages that point at your panel's records: drag a link to a record into the chat and it becomes a card.
Works with or without a websocket server: turn real-time on with one environment variable (Laravel Reverb, Pusher, Ably), or let the chat poll.
- Screenshots
- Features
- Requirements
- Installation
- Configuration — users, features, messages, interface, models & tables
- Record references — which records, who sees them, drag & drop, "Discuss in chat"
- Real-time delivery — Reverb, Docker, nginx, Pusher, polling
- Notifications
- Translations — your language, your own wording
- Customising — own models, audit trail, events
- Integration notes — custom themes, panels built by a package, several panels
- Troubleshooting
- AI agents
- Testing
#Screenshots
The Chat page — conversations, reactions, read receipts and record cards:

The slide-over pinned next to a record page, with "Attach current":

@mentions with autocomplete:

Dark mode:

#Features
| Conversations | Direct messages (one per pair) and groups: title, members, leave. The creator manages a group; whoever leaves keeps the history up to that moment. |
| @mentions | Type @ for the list of members. A mention is highlighted (your own name stands out) and always rings the bell. |
| Editing | ✏️ on your message or ↑ in an empty composer; edited messages are marked. Optional time window. |
| Reactions | Six emoji, one per person per message. |
| Read receipts | ✓ sent, ✓✓ read — in a group, read by every active member (the hint lists who has not). |
| Record references | Attach a record with a picker, by dropping a link to its page, or "Attach current" in the slide-over. |
| Where | A full Chat page and a top-bar button with a slide-over; on wide screens the slide-over can be pinned as a split screen that stays open across pages. |
| Unread | Counters in the navigation, on the button, in the browser tab title and on the favicon; a bell notification on the first unread message of a conversation and on every mention; a "Reply" toast. |
| Privacy | A conversation is visible to its members only — admins included. Messages are never deleted. |
| Languages | English and Ukrainian; add yours. |
Every feature can be switched off.
#Requirements
- PHP 8.3+
- Laravel 12 or 13
- Filament 5
#Installation
composer require asignua/filament-chat
php artisan filament-chat:install --migrate
filament-chat:install publishes config/filament-chat.php and the migration (4 tables), runs the
migrations with --migrate, links the agent skill with --skill and tells you what
is left. Bell notifications use Laravel's notifications table — php artisan make:notifications-table
if your app does not have it yet.
Register the plugin in your panel provider:
use Asignua\FilamentChat\FilamentChatPlugin;
public function panel(Panel $panel): Panel
{
return $panel
// ...
->databaseNotifications()
->plugin(FilamentChatPlugin::make());
}
That's it: a Chat page in the navigation and a chat button next to the user menu. The chat polls for news until you turn real-time on.
The stylesheet ships compiled and is linked after your panel's theme — no custom theme and no
@source lines are needed. It is published by php artisan filament:assets; if you commit
published assets, re-run it after upgrading the package.
#Configuration
Every setting lives in config/filament-chat.php (commented — read it once) and can also be set on
the plugin, which wins. Closures — who can be written to, how people are called, per-type rules for
references — exist on the plugin only, so the config stays cacheable.
#Users
FilamentChatPlugin::make()
// Who can be written to and added to groups. Default: everybody in the users table.
->users(fn (Builder $query) => $query->where('is_active', true)->whereHas('roles'))
// How a person is called. Default: users.name_attribute, then Filament's HasName, then `name`.
->userName(fn (User $user): string => "{$user->first_name} {$user->last_name}"),
// config/filament-chat.php
'users' => [
'model' => null, // null = auth.providers.users.model
'name_attribute' => null, // e.g. 'full_name' — an attribute or accessor
'search_columns' => ['name'], // e.g. ['first_name', 'last_name'] — the conversation search
'broadcast_key' => null, // e.g. 'ulid' — names the private channel (default: the primary key)
],
#Features
| Key | Plugin | Default | Off means |
|---|---|---|---|
features.groups |
->groups(false) |
true |
direct messages only; "New group" disappears, the server refuses groups |
features.reactions |
->reactions(false) |
true |
no emoji picker or chips; the server refuses reactions |
features.mentions |
->mentions(false) |
true |
no @ autocomplete, nothing highlighted or notified |
features.read_receipts |
->readReceipts(false) |
true |
no ✓ / ✓✓ |
features.editing.enabled / .window |
->editing(true, 15) |
true, null |
no editing; window — minutes after sending, null — any time |
Switching a feature off never deletes data.
#Messages
'messages' => [
'page_size' => 30, // loaded at once and per "Show earlier messages"
'max_length' => 5000,
],
#Interface
| Key | Plugin | Default |
|---|---|---|
ui.dock |
->dock(false) |
true — the top-bar button and slide-over; off: the Chat page only |
ui.pinnable |
->pinnable(false) |
true — "pin" the slide-over as a split screen (from lg) |
ui.tab_badge |
->tabBadge(false) |
true — unread count in the tab title and on the favicon |
ui.color |
->color('fuchsia') |
primary — group avatars and author names |
ui.slug |
->slug('messages') |
chat |
ui.navigation.group / .sort / .icon |
->navigationGroup() / ->navigationSort() / ->navigationIcon() |
null / 90 / chat bubbles |
->navigationGroup() and ->navigationIcon() also accept enums and closures (translated group names).
#Record references
A message may point at a record of your panel — an order, a customer, a page. It shows as a card with the record's icon, type and title, linking to its page.
#Which records
Register resources on the plugin:
use Asignua\FilamentChat\Support\References\ReferenceType;
FilamentChatPlugin::make()
->references([
ReferenceType::resource(OrderResource::class),
ReferenceType::resource(CustomerResource::class)->color('warning'),
]),
From the resource the chat takes the label, icon, record title, the link (the edit page for those who may edit, the view page for the rest) and the search of the picker (the globally searchable attributes). Everything can be overridden:
ReferenceType::make('invoice', Invoice::class)
->label('Invoice')
->icon(Heroicon::OutlinedDocumentCurrencyDollar)
->color('success')
->title(fn (Invoice $invoice): string => $invoice->number)
->url(fn (Invoice $invoice): ?string => InvoiceResource::getUrl('view', ['record' => $invoice]))
->search(fn (string $search) => Invoice::where('number', 'like', "%{$search}%")->limit(20)->get())
->searchColumns(['number', 'customer_name']) // instead of ->search()
->visibleUsing(fn (Invoice $invoice): bool => auth()->user()->can('view', $invoice)),
The key (invoice; order for ReferenceType::resource(OrderResource::class)) is stored with the
message — keep it stable.
Or every resource at once — no list to maintain:
// config/filament-chat.php
'references' => [
'all_resources' => true,
'except' => [ActivityLogResource::class, VisitorResource::class],
],
// or on the plugin
->references([
ReferenceType::allResources()->except([ActivityLogResource::class]),
ReferenceType::resource(OrderResource::class)->color('warning'), // explicit types win
]),
With "all resources" the key is the resource slug (orders, activity-log/activity-logs):
renaming a slug loses the link of old messages (they show "record deleted"). Register a type
explicitly where that matters.
'references' => ['enabled' => false] turns references off completely.
#Who sees a referenced record
A viewer who may not see the record gets its type only — no title, no link — and cannot attach it.
"May see" is references.authorize (or ->authorizeReferences() on the plugin):
| Value | Rule | A model without a policy |
|---|---|---|
'policy' (default) |
the model's view policy |
hidden |
'resource' |
the resource's canView() — Filament's own rule |
visible to everyone in the panel |
false |
no check | visible |
Watch out. Filament shows a resource whose model has no policy, but Laravel's
Gatedenies an ability nobody defined. With the default'policy', records of such a resource are visible in the panel yet can never be attached — dropping a link says so. Either add a policy (view→truefor a read-only resource) or switch to'resource'. A type's->visibleUsing()beats both.
#Attaching
- Picker — the 🔗 button next to the composer: type, then search.
- Drag & drop — drop any link to a record page into an open conversation: a table row, a link in a column, a global search result, the address bar. Links of resources that are not referenceable say so ("Orders cannot be attached to messages").
- Attach current — the slide-over opened on a record page offers that record in one click.
#Discuss in chat
A header action for record pages — to whom (a person or a group I am in) and the text; the record is attached:
use Asignua\FilamentChat\Actions\DiscussInChatAction;
protected function getHeaderActions(): array
{
return [DiscussInChatAction::make()];
}
It shows only for records of a referenceable type; it works as a table row action too.
#Real-time delivery
The chat works both ways — pick per environment:
FILAMENT_CHAT_REALTIME |
Behaviour |
|---|---|
false |
polling: an open conversation every 15 s, the unread counter every 60 s (polling.*) |
true |
new messages, reads, reactions and group changes arrive instantly; toasts appear on any page |
unset (null) |
on when the default broadcaster is reverb, pusher or ably |
Events carry identifiers only; the components re-read the data, so policies — not the socket
payload — decide what anyone sees. Each person listens on a private channel
filament-chat.user.{key}, authorised by the package. Events are broadcast immediately
(ShouldBroadcastNow) — no queue worker needed; if the socket is down the message is still saved
and the counters catch up by polling.
#Laravel Reverb
php artisan install:broadcasting --reverb
BROADCAST_CONNECTION=reverb
FILAMENT_CHAT_REALTIME=true
php artisan reverb:start
That is all: by default (realtime.echo = 'plugin') the plugin brings Laravel Echo itself — it
reuses the Echo that Filament already ships — configured from the broadcasting connection. It also
registers /broadcasting/auth if your app has not (realtime.register_auth_route).
Where the browser connects. PHP publishes to REVERB_HOST:REVERB_PORT; the browser connects to
the page's own host, protocol and default port unless told otherwise:
FILAMENT_CHAT_WS_HOST=ws.example.com # empty = the page's host
FILAMENT_CHAT_WS_PORT=8080 # empty = 443 on https, 80 on http
FILAMENT_CHAT_WS_SCHEME=https # empty = the page's protocol
-
Local, no Docker:
reverb:starton 8080 →FILAMENT_CHAT_WS_PORT=8080. -
Docker / Sail: PHP publishes to the service (
REVERB_HOST=reverb,REVERB_PORT=8080), the browser to the published port (FILAMENT_CHAT_WS_PORT=8080or whatever you map). -
Production behind nginx: proxy the websocket on the same host and leave the three variables empty:
location ^~ /app/ { proxy_pass http://reverb:8080; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_set_header Host $host; proxy_read_timeout 60s; }
#Pusher / Ably / another connection
Any Pusher-compatible connection works the same way (BROADCAST_CONNECTION=pusher). Pusher Channels
(cloud) is addressed by its cluster from broadcasting.connections.pusher.options.cluster.
To publish chat events on a connection other than the app's default:
FILAMENT_CHAT_BROADCAST_CONNECTION=reverb.
#Your own Echo
If your app already loads window.Echo (a Vite bundle), or you configured Filament's
filament.broadcasting.echo, set FILAMENT_CHAT_ECHO=host or =filament — the plugin then only
subscribes. An existing window.Echo is never replaced.
#Notifications
| Key | Default | |
|---|---|---|
notifications.database |
true |
the bell: the first unread message of a conversation (the rest only grow the counter) and every mention. Needs ->databaseNotifications(), the notifications table and Notifiable on the user model. |
notifications.toasts |
true |
a "Reply" toast for a message in another conversation (real-time only) |
#Translations
The chat speaks English and Ukrainian and follows your app's locale. Every string lives in one file,
resources/lang/en/chat.php.
Another language — create lang/vendor/filament-chat/{locale}/chat.php in your app with the same
keys (copy the English file and translate it):
// lang/vendor/filament-chat/de/chat.php
return [
'chat' => 'Chat',
'new_group' => 'Neue Gruppe',
// ...
];
Change a few phrases — the same file with only the keys you want to change; the rest still comes from the package:
// lang/vendor/filament-chat/en/chat.php
return [
'chat' => 'Messages',
];
Copy all package translations to edit them:
php artisan vendor:publish --tag=filament-chat-translations
Translated the chat into your language? A pull request with resources/lang/{locale}/chat.php is
very welcome.
#Customising
#Your own models and tables
Swap any model for a subclass and rename the tables before running the migration:
'models' => [
'conversation' => App\Models\ChatConversation::class, // extends Asignua\FilamentChat\Models\Conversation
// participant, message, reaction
],
'tables' => [
'conversations' => 'chat_conversations',
// participants, messages, reactions
],
#An audit trail
Messages, reactions and read pointers are deliberately not audited — an audit feed would let admins
read other people's conversations. For conversations use a subclass with your logging trait, and
listen to Asignua\FilamentChat\Events\GroupMembersChanged ($conversation, $before, $after —
member names) for membership, which lives in a pivot the model diff cannot see.
#Events
| Event | When |
|---|---|
Asignua\FilamentChat\Events\ChatUpdated |
anything changed in a conversation — broadcast to members |
Asignua\FilamentChat\Events\GroupMembersChanged |
a group was created, its members changed, someone left |
#Writing from your code
use Asignua\FilamentChat\Data\MessageData;
use Asignua\FilamentChat\Services\ChatService;
$chat = app(ChatService::class);
$conversation = $chat->startDirect($me, $colleague);
$chat->send($conversation, $me, MessageData::fromArray([
'body' => 'Please check @Olga Green',
'reference_type' => 'order',
'reference_id' => $order->id,
]));
Always go through ChatService — it checks membership, stores mentions, broadcasts and notifies.
#Integration notes
-
Custom themes. The stylesheet is linked after the panel's theme on purpose: a theme compiles the same Tailwind utilities (
.bg-white) and, loaded later, would beat the chat'sdark:variants. -
A panel built by a package (a CMS that owns its
PanelProvider): add the plugin when the panel registers —boot()is too late, the panel's routes already exist:// AppServiceProvider::register() $this->app->resolving(PanelRegistry::class, function (PanelRegistry $registry): void { $registry->get('cms', isStrict: false)?->plugin(FilamentChatPlugin::make()); }); -
Several panels. Register the plugin on one panel. Links in notifications lead to that panel.
-
Users of another panel (customers in a client cabinet): narrow
->users()to the people of the chat's panel. -
Search uses
LIKE: case-insensitive on MySQL/MariaDB and PostgreSQL; on SQLite for ASCII only.
#Troubleshooting
| Symptom | Cause / fix |
|---|---|
| "… cannot be attached to messages" on a drop | the resource is not referenceable — register it or use all_resources |
| "This link does not lead to a record…" | not a record page of this panel (a list, another host) |
| A record shows its type but "no access" | references.authorize = 'policy' and the model has no policy — see Who sees |
| The slide-over is white on a dark panel | stale published assets — php artisan filament:assets |
| "Laravel Echo cannot be found" in the console | real-time is on but no Echo: realtime.echo is host/filament without one, or window.EchoFactory is missing (Filament's core scripts) |
| Messages arrive only after ~15 s | real-time is off (FILAMENT_CHAT_REALTIME), or the socket is not reachable from the browser — check FILAMENT_CHAT_WS_* and the browser's network tab |
403 on /broadcasting/auth |
the user is not logged in on the web guard, or broadcast_key differs between server and channel |
| No bell notifications | ->databaseNotifications() on the panel, the notifications table, Notifiable on the user |
#AI agents
The package ships a skill for coding agents (Claude Code and others reading .claude/skills) —
installation, every option, record references, real-time setups and the traps above:
php artisan filament-chat:install --skill # links .claude/skills/filament-chat
It lives in resources/boost/skills/filament-chat/SKILL.md, where Laravel Boost looks for package skills.
#Testing
composer test # PHPUnit (Orchestra Testbench)
composer analyse # Larastan
composer format # Pint
npm run build # rebuild resources/dist/filament-chat.css after changing views
#License
MIT. See LICENSE.md.
Featured Plugins
A selection of plugins curated by the Filament team
Blueprint
Filament Blueprint is a premium Laravel Boost extension that helps AI agents produce accurate, detailed implementation plans and security reports for Filament apps.
Filament
Noir Theme
A theme that gives panels a focused, refined look with near-black surfaces, crisp actions, and restrained color.
Filament
Spotlight Pro
Browse your Filament Panel with ease. Filament Spotlight Pro adds a Spotlight like Command Palette to your Filament Panel.
Dennis Koch