blendbyte/filament-title-with-slug

Title + slug field for Filament v5 with auto-slug generation, live permalink preview, and inline editing.

Maintainers

Package info

github.com/blendbyte/filament-title-with-slug

pkg:composer/blendbyte/filament-title-with-slug

Statistics

Installs: 23 110

Dependents: 4

Suggesters: 0

Stars: 13

Open Issues: 0

v3.0.0 2026-04-16 12:53 UTC

This package is auto-updated.

Last update: 2026-04-16 13:05:12 UTC


README

filament-title-with-slug-banner

Title With Slug — Permalink Field for Filament v5

Latest Version on Packagist Total Downloads Tests Static Analysis PHP Version Software License

Title + slug field for Filament v5 with auto-slug generation, live permalink preview, and inline editing.

use Blendbyte\FilamentTitleWithSlug\TitleWithSlugInput;

TitleWithSlugInput::make()

Requirements

Package (blendbyte) Filament PHP Laravel Namespace
^3.x ^5.0 ^8.2^8.5 ^11 / ^12 / ^13* Blendbyte\FilamentTitleWithSlug
^2.x ^4.0 ^8.2 ^11 / ^12 Camya\Filament\Forms\Components
^1.x ^3.0 ^8.1 ^10 / ^11 / ^12 Camya\Filament\Forms\Components

*PHP 8.2 is not tested against Laravel 13.

Looking for Filament v2 support? The original package is at camya/filament-title-with-slug.

Features

  • Auto-generates a URL-safe slug from the title
  • Live permalink preview (https://example.com/blog/my-post)
  • Inline slug editing with OK / Cancel / Reset
  • "Visit" link to the published URL
  • Readonly mode for title and/or slug independently
  • Custom title field — pass any Filament field (e.g. a translatable input) as the title
  • Fully configurable: labels, placeholder, URL host & path, validation rules, slugifier
  • Translatable UI strings (7 languages included)
  • Dark mode supported
  • No Tailwind build step required

Table of Contents

Upgrading

All upgrades to v3.x share one breaking change: the namespace changed from Camya\Filament\Forms\Components (used in v1 and v2) to Blendbyte\FilamentTitleWithSlug.

Find every import of the component in your app and update it:

// Before (v1.x and v2.x)
use Camya\Filament\Forms\Components\TitleWithSlugInput;

// After (v3.x)
use Blendbyte\FilamentTitleWithSlug\TitleWithSlugInput;

The TitleWithSlugInput::make() call signature and all parameter names are unchanged.

From v2.x (Filament v4 → v5)

composer require blendbyte/filament-title-with-slug:^3.0
  1. Update the namespace as shown above.
  2. Remove any manual CSS import for this package from your Tailwind config or Vite pipeline — v3 registers its stylesheet automatically via Filament's asset system. No build step required.

New in v3: the titleField parameter for passing a custom field (e.g. translatable) as the title input.

From v1.x (Filament v3 → v5)

composer require blendbyte/filament-title-with-slug:^3.0
  1. Update the namespace as shown above.
  2. Remove any manual CSS imports (see above).
  3. See the Filament upgrade guide for app-level Filament changes.

From camya/filament-title-with-slug (Filament v2)

1. Swap the package:

composer remove camya/filament-title-with-slug
composer require blendbyte/filament-title-with-slug:^3.0

2. Update the namespace as shown above.

3. Re-publish config and translations if you had published them previously:

php artisan vendor:publish --tag="filament-title-with-slug-config" --force
php artisan vendor:publish --tag="filament-title-with-slug-translations" --force

4. Remove any manual CSS imports — v3 registers its stylesheet automatically.

5. See the Filament upgrade guide for app-level Filament changes.

Installation

composer require blendbyte/filament-title-with-slug

Optionally publish the config file:

php artisan vendor:publish --tag="filament-title-with-slug-config"

Translation

Publish the translation files:

php artisan vendor:publish --tag="filament-title-with-slug-translations"

Published translations land at lang/vendor/filament-title-with-slug.

Included languages: English, French, Brazilian Portuguese, German, Dutch, Indonesian, Arabic.

Added a translation? Share it on our GitHub Discussions page.

Usage & Examples

Basic usage

Add TitleWithSlugInput to any Filament form schema. It binds to title and slug by default:

use Blendbyte\FilamentTitleWithSlug\TitleWithSlugInput;

class PostResource extends Resource
{
    public static function form(Form $form): Form
    {
        return $form->schema([

            TitleWithSlugInput::make(),

        ]);
    }
}

Tip: Use ->columnSpan('full') to make the component span the full form width.

Custom title field

Pass any Filament field as the title input via titleField. This is useful when you need a translatable input or another custom field type from a third-party package:

TitleWithSlugInput::make(
    titleField: \Spatie\FilamentTranslatable\Forms\Components\TranslatableInput::make('title'),
)

The package automatically wires slug auto-generation onto the provided field. The field name is derived from getName(), so you don't need to pass fieldTitle separately unless you want to override it.

All title-specific parameters (titleLabel, titleRules, titlePlaceholder, etc.) are ignored when titleField is provided — configure those directly on your field before passing it in.

The titleAfterStateUpdated and titleFieldWrapper parameters still work regardless:

TitleWithSlugInput::make(
    titleField: MyTranslatableField::make('title')
        ->label('Post Title')
        ->required(),
    titleAfterStateUpdated: function (Set $set, string $state) {
        // runs after every title update
    },
)

Change model field names

Default field names are title and slug. Override them:

TitleWithSlugInput::make(
    fieldTitle: 'name',
    fieldSlug: 'identifier',
)

Change labels and placeholder

TitleWithSlugInput::make(
    urlPath: '/book/',
    urlVisitLinkLabel: 'Visit Book',
    titleLabel: 'Title',
    titlePlaceholder: 'Insert the title...',
    slugLabel: 'Link:',
)

Permalink preview: hide host

TitleWithSlugInput::make(
    urlHostVisible: false,
)

Permalink preview: change host and path

TitleWithSlugInput::make(
    urlPath: '/category/',
    urlHost: 'https://project.local',
)

Visit link via named route

Use a named route to generate the Visit link URL:

TitleWithSlugInput::make(
    urlPath: '/product/',
    urlHost: 'camya.com',
    urlVisitLinkRoute: fn(?Model $record) => $record?->slug
        ? route('product.show', ['slug' => $record->slug])
        : null,
)

Because the Visit URL is now route-generated, you can use a short urlHost just for the permalink preview display.

Hide the Visit link

TitleWithSlugInput::make(
    urlVisitLinkVisible: false,
)

Style the title input

Pass extra HTML attributes directly to the title <input> element:

TitleWithSlugInput::make(
    titleExtraInputAttributes: ['class' => 'italic'],
)

Readonly title or slug

Lock either field, optionally based on context:

TitleWithSlugInput::make(
    titleIsReadonly: fn($context) => $context === 'edit',
    slugIsReadonly: fn($context) => $context === 'edit',
)

When slugIsReadonly is true the slug row renders as a static permalink display (no edit link, no action buttons).

Validation rules

TitleWithSlugInput::make(
    titleRules: [
        'required',
        'string',
        'min:3',
        'max:120',
    ],
)

A unique rule is automatically applied to the slug. To customize it, see Custom unique validation.

Custom unique validation

Pass an array of named arguments that map to Filament's ->unique() method:

TitleWithSlugInput::make(
    slugRuleUniqueParameters: [
        'modifyRuleUsing' => fn(Unique $rule) => $rule->where('is_published', 1),
        'ignorable' => fn(?Model $record) => $record,
    ],
)

Available keys: ignorable, modifyRuleUsing, ignoreRecord, table, column.

Custom error messages

Override validation messages in your resource class:

protected $messages = [
    'data.slug.regex' => 'Invalid slug. Use only a–z, 0–9, and hyphens.',
];

Custom slugifier

Replace the default Str::slug() with your own closure:

TitleWithSlugInput::make(
    slugSlugifier: fn($string) => preg_replace('/[^a-z]/', '', $string),
    slugRuleRegex: '/^[a-z]*$/',
)

Dark mode

Empty homepage slug

Remove the slug's required rule, then use / as the slug value to represent the homepage. The / bypasses the auto-regeneration that would trigger on an empty value:

TitleWithSlugInput::make(
    slugRules: [],
)

Within a repeater

Repeater::make('FAQEntries')
    ->relationship()
    ->collapsible()
    ->schema([

        TitleWithSlugInput::make(
            fieldTitle: 'title',
            fieldSlug: 'slug',
            urlPath: '/faq/',
            urlHostVisible: false,
            titleLabel: 'Title',
            titlePlaceholder: 'Insert FAQ title...',
        ),

    ]),

URL slug sandwich

Place the slug in the middle of a path, e.g. /books/my-slug/detail/:

TitleWithSlugInput::make(
    urlPath: '/books/',
    urlVisitLinkRoute: fn(?Model $record) => $record?->slug
        ? '/books/' . $record->slug . '/detail'
        : null,
    slugLabelPostfix: '/detail/',
    urlVisitLinkLabel: 'Visit Book Details',
),

Slug as subdomain

TitleWithSlugInput::make(
    fieldSlug: 'subdomain',
    urlPath: '',
    urlHostVisible: false,
    urlVisitLinkLabel: 'Visit Domain',
    urlVisitLinkRoute: fn(?Model $record) => $record?->slug
        ? 'https://' . $record->slug . '.camya.com'
        : null,
    slugLabel: 'Domain:',
    slugLabelPostfix: '.camya.com',
),

Config file defaults

Publish the config to set global defaults:

php artisan vendor:publish --tag="filament-title-with-slug-config"

Published at config/filament-title-with-slug.php:

[
    'field_title' => 'title',          // Override per-field with fieldTitle:
    'field_slug'  => 'slug',           // Override per-field with fieldSlug:
    'url_host'    => env('APP_URL'),   // Override per-field with urlHost:
]

All available parameters

All parameters are optional and use named argument syntax. Parameters marked (ignored when titleField is set) only apply to the default TextInput.

TitleWithSlugInput::make(

    // Model fields
    fieldTitle: 'title',
    fieldSlug: 'slug',

    // Custom title field — replaces the default TextInput
    titleField: SomeField::make('title'),

    // URL
    urlPath: '/blog/',
    urlHost: 'https://www.example.com',
    urlHostVisible: true,
    urlVisitLinkLabel: 'View',
    urlVisitLinkRoute: fn(?Model $record) => $record?->slug
        ? route('post.show', ['slug' => $record->slug])
        : null,
    urlVisitLinkVisible: true,

    // Title — ignored when titleField is provided
    titleLabel: 'The Title',
    titlePlaceholder: 'Post Title',
    titleExtraInputAttributes: ['class' => 'italic'],
    titleRules: ['required', 'string'],
    titleRuleUniqueParameters: [
        'modifyRuleUsing' => fn(Unique $rule) => $rule->where('is_published', 1),
        'ignorable' => fn(?Model $record) => $record,
    ],
    titleIsReadonly: fn($context) => $context !== 'create',
    titleAutofocus: true,

    // Title callbacks — work with both default TextInput and custom titleField
    titleAfterStateUpdated: function ($state) {},
    titleFieldWrapper: fn($field) => $field,

    // Slug
    slugLabel: 'The Slug:',
    slugRules: ['required', 'string'],
    slugRuleUniqueParameters: [
        'modifyRuleUsing' => fn(Unique $rule) => $rule->where('is_published', 1),
        'ignorable' => fn(?Model $record) => $record,
    ],
    slugIsReadonly: fn($context) => $context !== 'create',
    slugSlugifier: fn($string) => Str::slug($string),
    slugRuleRegex: '/^[a-z0-9\-\_]*$/',
    slugAfterStateUpdated: function ($state) {},
    slugLabelPostfix: '/suffix',

)->columnSpan('full'),

Credits

Originally created by Andreas Scheibel (camya). Inspired by packages from awcodes and the work of spatie. Tests built with Pest.

Please see the release changelog for version history, and contributing for how to get involved. Security vulnerabilities can be reported via our security policy.

Maintained by Blendbyte

Blendbyte

This project is maintained by Blendbyte — a team of engineers with 20+ years of experience building cloud infrastructure, web applications, and developer tools. We use these packages in production ourselves and actively contribute to the open source ecosystem we rely on every day. Issues and PRs are always welcome.

🌐 blendbyte.com · 📧 hello@blendbyte.com