There are times when you may need to generate a slug, either for a blog article or a page. Given there are multiple ways to skin a cat here, I'll review how we can do this using Filament so that the user can override the slug via a friendly interface.
What is a Slug?
A slug is often seen in page URLs, that identify a unique page in a way that's easy to read. They are considered a good practice for SEO as keywords can be inserted whilst maintaining a common format.
ie, https://harrk.dev/filament-generating-a-slug-from-a-title/
the slug is the last part of the URL, and mostly copies what the subject of page is about. Special characters are removed, spaces are hyphenated, and all uppercase characters are swapped with lowercase variants.
Creating a Slug with Filament
Following the Filament documentation, you can hook into the afterStateUpdated method in order to retrieve the latest value and mutate the result into another field. Fortunately, Filament gives you the code for this exact use-case but it comes with an issue which I'll come to later.
The following code taken from their documentation shows how you can make a field update another field. In this case, by taking a title field and slugifying its value into another field.
If you're attempting this locally, the slug field should update in live time without any issue.
However, if you throttle your connection or try attempting this on a server with some latency. The title field will jank about as you are updating the value quicker than the server is returning the previous state. Resulting in the server response overwriting what you entered.
To remedy this, you need to apply a delay to the field before it fires off a state update to the server. By passing a value for a debounce
, the field will wait X milliseconds since the last input before it calls home. Should the field be updated within X milliseconds, it'll reset the counter back to 0 instead of queuing several calls.
use Filament\Forms\Components\TextInput;
use Filament\Forms\Set;
use Illuminate\Support\Str;
TextInput::make('title')
->live(debounce: 250) // Method 1: Pass a debounce value here
->debounce(250) // or Method 2: Use the debounce method
->afterStateUpdated(fn (Set $set, ?string $state) => $set('slug', Str::slug($state)))
TextInput::make('slug')