Filament RichEditor: Add @Mentions with Dynamic Database Search

2026-04-13 Filament v4/5

You're building a ticketing system and your users need to @mention other users inside a RichEditor field, just like on GitHub or Slack. The built-in RichEditor now ships with a MentionProvider that handles trigger characters, search dropdowns, and rendering mentions as clickable links.


Static Mentions (Quick Start)

The simplest setup uses a hardcoded array. This works if your list is small and doesn't change often.

app/Filament/Resources/TicketResource.php

use Filament\Forms\Components\RichEditor;
use Filament\Forms\Components\RichEditor\MentionProvider;
 
RichEditor::make('content')
->mentions([
MentionProvider::make('@')
->items([
1 => 'Jane Doe',
2 => 'John Smith',
3 => 'Alex Johnson',
]),
])

This works, but it doesn't scale. If you have hundreds of users, you're loading them all upfront for every form render. A better approach is to search the database dynamically.


Dynamic Mentions from the Database

Replace items() with getSearchResultsUsing() to query your users table on demand. The callback receives the search term and returns an array of [id => label] pairs.

There's one gotcha here. When you use dynamic search, Filament only stores the mention's ID in the content. When the form loads again (say, on an Edit page), it needs to resolve those IDs back into display names. That's what getLabelsUsing() does.

app/Filament/Resources/TicketResource.php

use App\Models\User;
use Filament\Forms\Components\RichEditor;
use Filament\Forms\Components\RichEditor\MentionProvider;
 
RichEditor::make('content')
->mentions([
MentionProvider::make('@')
->getSearchResultsUsing(fn (string $search): array =>
User::query()
->where('name', 'like', "%{$search}%")
->orderBy('name')
->limit(10)
->pluck('name', 'id')
->all()
)
->getLabelsUsing(fn (array $ids): array =>
User::query()
->whereIn('id', $ids)
->pluck('name', 'id')
->all()
),
])

Without getLabelsUsing(), existing mentions in saved content will show raw IDs instead of names when the form is loaded for editing. Don't skip it.


Multiple Trigger Characters

You can combine multiple MentionProvider instances for different types of mentions. A common pattern is @ for users and # for tags or labels.

app/Filament/Resources/TicketResource.php

use App\Models\Tag;
use App\Models\User;
use Filament\Forms\Components\RichEditor;
use Filament\Forms\Components\RichEditor\MentionProvider;
 
RichEditor::make('content')
->mentions([
MentionProvider::make('@')
->getSearchResultsUsing(fn (string $search): array =>
User::query()
->where('name', 'like', "%{$search}%")
->orderBy('name')
->limit(10)
->pluck('name', 'id')
->all()
)
->getLabelsUsing(fn (array $ids): array =>
User::query()
->whereIn('id', $ids)
->pluck('name', 'id')
->all()
),
MentionProvider::make('#')
->getSearchResultsUsing(fn (string $search): array =>
Tag::query()
->where('name', 'like', "%{$search}%")
->orderBy('name')
->limit(10)
->pluck('name', 'id')
->all()
)
->getLabelsUsing(fn (array $ids): array =>
Tag::query()
->whereIn('id', $ids)
->pluck('name', 'id')
->all()
),
])

Each trigger character gets its own provider, its own search callback, and its own label resolver.


Rendering Mentions as Clickable Links

Storing mentions is only half the job. When you display the saved content on a View page or in a Blade template, you probably want @Jane Doe to link to that user's profile.

Filament provides RichContentRenderer for this. Pass the same MentionProvider config and add the url() method.

resources/views/tickets/show.blade.php

use Filament\Forms\Components\RichEditor\MentionProvider;
use Filament\Forms\Components\RichEditor\RichContentRenderer;
 
$html = RichContentRenderer::make($ticket->content)
->mentions([
MentionProvider::make('@')
->getLabelsUsing(fn (array $ids): array =>
User::query()
->whereIn('id', $ids)
->pluck('name', 'id')
->all()
)
->url(fn (string $id, string $label): string =>
route('filament.admin.resources.users.view', $id)
),
MentionProvider::make('#')
->getLabelsUsing(fn (array $ids): array =>
Tag::query()
->whereIn('id', $ids)
->pluck('name', 'id')
->all()
)
->url(fn (string $id, string $label): string =>
route('filament.admin.resources.tags.view', $id)
),
])
->toHtml();

Then in your Blade:

<div class="prose">
{!! $html !!}
</div>

How It Works

A few things to keep in mind:

  1. getSearchResultsUsing() is called on every keystroke after the trigger character. Limit your query with ->limit(10) to keep things fast.
  2. getLabelsUsing() receives an array of all mention IDs found in the current content. It runs once when the form loads, not per mention.
  3. The url() method on the renderer only affects the HTML output. Inside the editor, mentions always appear as non-editable inline tokens.

You can also extract shared MentionProvider config into a helper method or a dedicated class if you're reusing it across multiple resources and the renderer. That way, the form definition and the Blade rendering always stay in sync.

A few of our Premium Examples: