Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Filament Mention Laravel Package

asmit/filament-mention

asmit/filament-mention brings @mentions to Filament RichEditor fields. It provides searchable, model-backed mention suggestions, customizable item rendering, and straightforward form integration, making it easy to tag users or other mentionable records directly within rich text.

View on GitHub
Deep Wiki
Context7

Getting Started

Install the package and publish its config so you have something to edit rather than a vendor file:

composer require asmit/filament-mention
php artisan vendor:publish --tag="filament-mention-config"

Register the plugin on the panel (this is the path most installations use, since the mention extension has to be injected into Filament's RichEditor asset bundle globally):

use Asmit\FilamentMention\FilamentMentionPlugin;

public function panel(Panel $panel): Panel
{
    return $panel
        ->plugins([
            FilamentMentionPlugin::make(),
        ]);
}

Then open config/filament-mention.php first — that is the single source of truth for what is mentionable, how the dropdown searches, and how many results are returned. A typical shape is:

return [
    'model'         => App\Models\User::class,
    'search_column' => 'name',   // or 'username'
    'limit'         => 5,
    'trigger'       => '@',
];

If your version of the package ships a field-level macro instead of (or in addition to) a panel plugin, you will see it documented as RichEditor::make('body')->mentionable() — check the installed version's README before wiring the panel provider, because the registration style changed between early releases.

First use case: a comment/ticket body field. Add a RichEditor::make('body') to a form, confirm typing @ opens the suggestion dropdown with records from the configured model, and that selecting one inserts the handle into the editor state. Once that round-trips through ->save(), you have the 90% case working.

Implementation Patterns

Keep mentionables out of the global config when you have more than one editor. Config is a singleton; if your Ticket editor should mention only staff and your Comment editor should mention everyone, define the model/search via the plugin or field instance rather than the published config, or build a small factory that returns a configured RichEditor per context.

Scope the suggestion query for multi-tenancy. The package resolves candidates from the configured Eloquent model. If you run a tenant-scoped panel, wrap the model resolution in your own query scope (a mentionable() static on the model, or a custom resolver if the package exposes one) so a user in tenant A cannot enumerate tenant B's users by typing @a.

Store both the display string and the resolved IDs. The editor inserts @Jane Doe as text. That text is not a stable reference — people rename, duplicate names exist, and handles with spaces break naive regex parsing. The robust pattern is to parse mentions on save and persist them to a mentions morph table:

// App\Models\Post
public function mentions(): MorphMany
{
    return $this->morphMany(Mention::class, 'mentionable');
}

// in the saving hook
$post->mentions()->delete();
preg_match_all('/@([\p{L}\p{N}_ .-]+)/u', strip_tags($post->body), $m);
$ids = User::whereIn('name', $m[1])->pluck('id');
$post->mentions()->createMany($ids->map(fn ($id) => ['user_id' => $id]));

Then notifications/@-badges read from the pivot table and never re-parse HTML.

Render through a dedicated presenter, not raw HTML. Output the editor HTML with Filament\Support\helpers\html-style sanitization or a RichContentRenderer, and post-process mention spans into profile links server-side. Keeping linkification in one presenter means the styling of a mention is a single place to change.

Reuse across panels by extracting a form schema method. If two panels both need a mentionable editor, put the RichEditor definition in a static TicketForm::schema() and call it from both, so the extension config cannot drift.

Testing: Livewire tests exercise the form state, not the JS dropdown — set the field value directly (fillForm(['body' => 'Hi @Jane'])) and assert on the persisted mentions. The TipTap dropdown itself needs a browser test (Dusk/Playwright); do not try to assert it from Pest.

Gotchas and Tips

  • Assets must be republished after upgrades. The extension is a JS module registered as a Filament asset. After composer update, run php artisan filament:assets (and php artisan optimize:clear) or you will silently keep the old bundle and wonder why config changes do nothing.
  • Config caching bites. config:cache in production means the published config is frozen; changing model on the server without re-caching looks like the package ignoring you.
  • The inserted text is not an ID. Treat everything the editor produces as untrusted, user-editable text. A user can type @admin by hand without ever selecting from the dropdown, so never grant permissions from parsed mentions without verifying the target exists and is visible to the author.
  • XSS: the RichEditor output is HTML. Run it through a sanitizer (e.g. mews/purifier) before {!! !!}. This is a general Filament issue but mention output is a common injection point because people assume the dropdown constrained input.
  • Names with spaces and punctuation. Default search/filter tokenization usually splits on whitespace, so @Jane Doe truncates to @Jane. Prefer a unique handle (username) as the inserted value and keep the display name for rendering.
  • Multi-tenant leakage is the highest-severity risk here: any suggestion endpoint that returns users must be scoped by the same policy as your resource. Check the network response for the suggestion request, not just the UI.
  • Filament major-version coupling. This package tracks Filament 3's asset/RichEditor internals; a Filament 4 bump is likely to break it. Pin the Filament constraint in composer.json and budget a spike before upgrading the panel.
  • Debugging path: browser console for the TipTap/extension registration error → view-source for whether the JS asset is actually loaded → php artisan filament:assets → then the package source in vendor/asmit/filament-mention/resources/js. The package is small enough (44 stars, single maintainer) that reading the extension source is faster than waiting for docs.
  • Extension points to lean on: the published config for model/search, the published Blade view for dropdown markup styling, and RichEditor::configureUsing() for applying the extension panel-wide without touching every form. If you need behaviour the package does not expose, write your own TipTap Mention extension and register it as an asset — you are not locked in, but you will be maintaining it.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core