Free·

Generating a slug from a title

Generating a slug from a title using Javascript

Filament v4 introduces new ways to run JavaScript on the client side. This is especially handy when you need to update form field states instantly, without sending requests to the server.

The Problem

In Filament v3, generating a slug from a title usually meant relying on Livewire. You'd watch the title field and send a Livewire request to update the slug. If you wanted to avoid that, you probably wrote custom JavaScript to handle it purely on the client side.

With Filament v4, this workflow is now built in. You can use the afterStateUpdatedJs method to handle it natively, without Livewire or extra scripts:

app/Filament/Resources/Posts/Schemas/PostForm.php
<?php

namespace App\Filament\Resources\Posts\Schemas;

use App\Enums\PostStatus;
use App\Models\Post;
use Filament\Forms\Components\DateTimePicker;
use Filament\Forms\Components\FileUpload;
use Filament\Forms\Components\RichEditor;
use Filament\Forms\Components\TagsInput;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\ToggleButtons;
use Filament\Schemas\Components\Tabs;
use Filament\Schemas\Schema;
use Filament\Support\Icons\Heroicon;

class PostForm
{
    public static function configure(Schema $schema): Schema
    {
        return $schema
            ->columns(null)
            ->components([
                Tabs::make('Tabs')
                    ->vertical()
                    ->schema([
                        Tabs\Tab::make('Post')
                            ->columns(2)
                            ->icon(Heroicon::OutlinedDocument)
                            ->schema([
                                TextInput::make('title')
                                    ->afterStateUpdatedJs(<<<'JS'
                                        $set('slug', slug($state));
                                    JS)
                                    ->required(),
                                TextInput::make('slug')
                                    ->required(),
                                RichEditor::make('content')
                                    ->columnSpanFull(),
                            ]),
                        Tabs\Tab::make('Images')
                            ->icon(Heroicon::OutlinedPhoto)
                            ->columns(2)
                            ->schema([
                                FileUpload::make('thumbnail')
                                    ->panelLayout('grid')
                                    ->disk('public')
                                    ->image(),
                                FileUpload::make('featured_image')
                                    ->panelLayout('grid')
                                    ->disk('public')
                                    ->image(),

                                FileUpload::make('gallery')
                                    ->panelLayout('grid')
                                    ->disk('public')
                                    ->multiple()
                                    ->columnSpanFull(),
                            ]),
                        Tabs\Tab::make('Tags')
                            ->icon(Heroicon::OutlinedTag)
                            ->schema([
                                TagsInput::make('tags')
                                    ->placeholder('Add tags...')
                                    ->columnSpanFull(),
                            ]),
                        Tabs\Tab::make('Status')
                            ->icon(Heroicon::OutlinedQuestionMarkCircle)
                            ->badge(fn (?Post $record) => $record
                                ->status
                                ->value ?? 'draft'
                            )
                            ->badgeColor(fn (?Post $record) => match ($record?->status) {
                                default => 'gray',
                                PostStatus::Draft => 'gray',
                                PostStatus::Published => 'success',
                                PostStatus::Archived => 'danger',
                            })
                            ->columns(2)
                            ->schema([
                                ToggleButtons::make('status')
                                    ->options(PostStatus::class)
                                    ->default('draft')
                                    ->required()
                                    ->inline(),
                                DateTimePicker::make('published_at')
                                    ->label('Publish At')
                                    ->default(now())
                                    ->required(),
                            ]),
                    ]),
            ]);
    }
}
app/Providers/AppServiceProvider.php
<?php

namespace App\Providers;

use Filament\Support\Assets\Js;
use Filament\Support\Facades\FilamentAsset;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    /**
     * Register any application services.
     */
    public function register(): void
    {
        //
    }

    /**
     * Bootstrap any application services.
     */
    public function boot(): void
    {
        FilamentAsset::register([
            Js::make(
                'generate-slug',
                'https://cdn.jsdelivr.net/npm/[email protected]/slug.min.js'),
        ]);
    }
}

This approach keeps everything on the client side. No Livewire requests are sent to the server, which makes the form feel faster and more responsive.

Conclusion

Filament v4 brings many improvements, and one of the most practical is its ability to handle JavaScript interactions directly. By using afterStateUpdatedJs, you can cut down on unnecessary Livewire calls and keep your forms snappy.

Download source code

Download on GitHub

Join the Discussion

Run project on Firebase Studio

  • Open studio.firebase.google.com and import the repository https://github.com/wiremodel/simple-blog
  • Run the project. Once it's up, switch to the correct branch:
git checkout v4/generate-slug

Update dependencies and migrate the database:

composer update -W
php artisan migrate:fresh --seed
Make sure your .env file has APP_URL set to the URL provided by Firebase Studio.