
Summary
Permalink field for Filament: title + slug in one block, live URL preview, inline editing, Copy/Visit/Regenerate buttons, and uniqueness validation.Spatielaravel-sluggableis optional. By default, the slug is generated from the title usingStr::slug()in the browser and saved to the database like a regular form field. You only need to add the Spatie package if you want the same rules as on model saving (such as-2,-3suffixes,preventOverwrite, etc.).
| Slug field class | Bjanczak\FilamentFlexFields\Filament\Forms\Components\SlugField |
| Title + slug factory | Bjanczak\FilamentFlexFields\Filament\Forms\Components\TitleSlugField |
| Convenience schema | SlugField::withTitle() → returns a FusedGroup (same as TitleSlugField::make()) |
| State type | string|null (normalized slug; homepage slug is '/' when enabled) |
| FieldType | slug |
| Spatie | Optional — see Spatie laravel-sluggable integration |
Start here — integration without Spatie (default)
No extra packages required besidesfilament-flex-fields. The model does not need any traits or slug options—just a database column and $fillable configuration.
Who is responsible for what
| What | Who does it | Do you need to write code? |
|---|---|---|
| Slug preview while typing the title | SlugField (Alpine + Str::slug) | No — works automatically |
Saving the slug value to the database | Filament + Eloquent | Yes — database column + $fillable |
| Slug uniqueness in the form | SlugField (unique rule) | No — enabled by default |
Suffix -2 on database collision | Only Spatie (HasSlug) | No — without Spatie, the slug must be unique in the form |
| Create vs Edit behaviour | TitleSlugField | No — by default, the slug does not change on edit |
Checklist — 4 steps
Step 1 — Migration
Step 2 — Model (minimal, without Spatie)
use HasSluganigetSlugOptions()- an observer generating the slug
- mutatora
setSlugAttribute composer require spatie/laravel-sluggable
title—from the form data.
Step 3 — Filament Resource (minimum)
FusedGroup.
Step 4 — Config (optional, for permalink path)
If you want to seehttps://your-domain.com/blog/my-post under the slug:
Parameters of TitleSlugField::make() — what is available?
| Parameter | Required? | Default | Description |
|---|---|---|---|
| (żaden) | — | — | TitleSlugField::make() wystarczy na start |
fieldTitle | Nie | 'title' | Alternative title column/field name |
fieldSlug | Nie | 'slug' | Alternative slug column/field name |
urlHost | Nie | z config lub null | Full URL preview host |
urlPath | Nie | null | Path prefix, e.g. /blog/ |
preserveSlugOnEdit | Nie | true | false = slug always syncs with title |
translatableLocales | Nie | z config lub null | Enables multi-language tabs (TranslatableFields) |
slugSourceLocale | Nie | app.locale / pierwszy locale | Which title language drives slug generation |
requiredTitleLocales | Nie | only slugSourceLocale | 'all', ['en'] lub null — which title locales are required |
spatieTranslatable | Nie | false | Config flag for Spatie models — see Translatable titles |
titleLocaleConfigurator | Nie | null | fn (FlexTextInput $field, string $locale) => $field |
translatableFieldsConfigurator | Nie | null | fn (TranslatableFields $fields) => $fields->… — custom configuration of title tabs |
spatieModel | Nie | null | Only for Spatie Sluggable (HasSlug) — do not confuse with Translatable |
What happens automatically (without Spatie)
| Event | Behaviour |
|---|---|
| Create — user types title | Slug updates live (Hello World → hello-world) |
| Edit — user changes title | Slug does not change (preserves published URL) |
| Edit — manual slug edit | Auto-sync turns off; badge shows Custom; Regenerate appears |
| Save | Form slug value → slug database column |
| Duplicate slug | Form validation error (before database write) |
Slug generation without Spatie (technical)
Four ways to add title + slug
When to switch to Spatie?
Only add Spatie when you need model-level hooks that the form alone cannot handle:- automatic
-2,-3suffixes on database collisions preventOverwrite— nigdy nie nadpisuj sluga po publikacjiskipGenerateWhen,extraScope, wiele pól źródłowych- using the same
SlugOptionsin form preview and onsave()
laravel-sluggable integration.
Common issues (without Spatie)
| Symptom | Cause | Fix |
|---|---|---|
| Slug does not save | Missing slug in $fillable | Add to $fillable |
| No URL preview under slug | url_host is null | Set APP_URL in .env or ->urlHost(...) |
| Slug does not update from title | Manual edit disabled auto-sync | Click Regenerate |
| Validation: slug already exists | Duplicate in database | Change slug or delete the old record |
name / handle fields instead of title/slug | Default field names | fieldTitle: / fieldSlug: lub config |
Next sections: Default form layout → Installation → Config → Full Example → Spatie Integration
Default form layout (FusedGroup)
TitleSlugField::make() zawsze zwraca Filament\Schemas\Components\FusedGroup — ten sam układ z parametrami lub bez:
Important:spatieModelchanges only the slug preview generation logic (server +SlugOptions). It does not change the form layout.
Co jest wewnątrz FusedGroup
| # | Komponent | State path (domyślnie) | Widoczny? | Rola |
|---|---|---|---|---|
| 1 | FlexTextInput | title | Tak | Pole tytułu, live(), auto-sync do sluga |
| 2 | Hidden | slug_auto_update_disabled | Nie | Flaga: użytkownik ręcznie edytował slug |
| 3 | SlugField | slug | Tak | Permalink, inline edit, akcje Copy/Visit/… |
fff-title-slug-fused-group (without the standard Filament border between fields).
Domyślny wygląd (ASCII)
Whenconfig('filament-flex-fields.slug.url_host') is set (e.g. APP_URL):
url_host is null (no URL preview in config):
Table of default visual values
| Element | Domyślna wartość | Skąd się bierze |
|---|---|---|
| Title field name | title | config('filament-flex-fields.slug.field_title') |
| Slug field name | slug | config('filament-flex-fields.slug.field_slug') |
| Label title | "Title" | Str::headline($fieldTitle) |
| Placeholder title | "Title" | j.w. |
| Label slug | ukryty | slugLabel: null → hiddenLabel() |
| Slug size | md (40px) | config('filament-flex-fields.ui.slug_size') |
| Slug variant | primary | config('filament-flex-fields.ui.slug_variant') |
| Permalink host | APP_URL lub null | config('filament-flex-fields.slug.url_host') |
| Etykiety przycisków | tekst + ikona | config('filament-flex-fields.slug.action_button_labels') |
| Ikony | Gravity UI | np. gravityui-pencil, gravityui-copy |
| Badge | Auto / Custom | Alpine — po ręcznej edycji sluga |
The same layout — three ways to call
Instalacja i assety
Pakiet jest częściąjanczakb/filament-flex-fields. Ścieżka bez Spatie wymaga tylko assetów pakietu — bez dodatkowych composer require.
Opcjonalnie: Spatie (dopiero gdy potrzebujesz)
Instaluj tylko jeśli model maHasSlug / getSlugOptions() i chcesz zgodny podgląd w formularzu:
TitleSlugField::make() działa w pełni — generowanie przez Str::slug() w przeglądarce.
Konfiguracja pakietu (config/filament-flex-fields.php)
Opublikuj config:
Full Example from scratch (migration -> model -> Resource)
Continuation of the Start here — integration without Spatie section. Steps 1–3 are the minimum; step 4 (Spatie) is optional.
1. Migracja
2. Model (bez Spatie — wystarczy na produkcję)
HasSlug ani getSlugOptions() — chyba że przechodzisz na krok 4 poniżej.
3. Filament Resource (without Spatie)
4. (Optional) The same Resource with Spatie
Add this step only when you need suffixes,preventOverwrite, or other SlugOptions rules during save.
Dwie warstwy: formularz pokazuje podgląd sluga na żywo; przy zapisie rekordu SpatieHasSlugmoże dodać suffix (-2) lub zastosowaćpreventOverwrite— to normalne.
Cookbook — typical scenarios
Scenario 1: Blog — create + edit (default behaviour)
- Create: tytuł → slug na żywo.
- Edit: zmiana tytułu nie zmienia sluga.
Scenario 2: Always sync slug with title
Scenario 3: Slug read-only on edit
Scenario 4: Custom title (RichEditor) + slug
Scenario 5: Slug uniqueness within tenant
Scenario 6: No permalink — slug field only
Scenario 7: CMS Homepage (/)
Standalone SlugField (matches playground Homepage slug):
TitleSlugField:
Scenario 8: Repeater — row with title and slug
sections.0.title → sections.0.slug) są rozwiązywane automatycznie.
Scenario 9: Manual slug only (no title, no auto-generate)
Use when there is no title field — user types the slug by hand. This is not the same as playgroundslug__standalone (that name means “slug field alone in the layout”, but it still uses ->source('title')).
Scenario 10: Form read-only (whole field)
Matches playground Form readonly:Scenario 11: Spatie + multiple source fields (optional package)
Quick Start — Filament Resource (create + edit)
Summary of the Start here section — without Spatie:| Event | Behaviour |
|---|---|
| User types title on create | Slug updates live (hello-world) |
| User opens edit | Existing slug is preserved when title changes |
| User clicks Edit on slug and saves a custom value | Auto-sync stops; badge shows Custom |
| User clicks Regenerate (after manual edit) | Slug is rebuilt from current title |
| Duplicate slug on save | Validation error (unique rule) |
How slug generation works
| Layer | Responsibility |
|---|---|
| SlugField (browser / optional server preview) | Shows what the slug will look like while typing |
Eloquent model + Spatie HasSlug | Final slug on create / update (unique suffix, preventOverwrite, etc.) |
SlugOptions as your model so previews match production rules (separator, language, max length, multi-field sources, extraScope, suffix start, Closure sources).
Create vs edit
Default (preserve slug on edit)
Always sync slug from title
Read-only slug on edit only
Read-only title
Universal locale tabs: For any translatable attribute (title, body, metadata, …), use the dedicated TranslatableFields component. The section below covers TitleSlugField only — translatable titles with a single shared slug.
Translatable titles (single slug)
For generic translatable fields (body, excerpt, metadata, …) without slug coupling, use TranslatableFields instead.Optional multi-language title with one shared slug. Locale switching uses package
TranslatableFields (built on SegmentTabs).
| Layer | Behaviour |
|---|---|
| Title form state | Dot paths: title.pl, title.en, … |
| Title DB state | Nested array / JSON: {"pl":"…","en":"…"} |
| Slug form + DB | Single string — not translatable |
| Auto-sync | Only when the source locale tab changes |
| Required title | By default, only the source locale is required() |
What is implemented today
| Feature | Status |
|---|---|
TranslatableFields per locale | Yes |
translatableFieldsConfigurator passthrough | Yes — RTL, badges, modifyFieldsUsing(), Spatie flag, etc. |
slugSourceLocale — pick slug source language | Yes |
Works without Spatie (array / json cast) | Yes |
| Hydrate edit form from JSON column | Yes |
Auto-detect HasTranslations on record (when package installed) | Yes — getTranslation($attribute, $locale, false) per tab |
spatieTranslatable: true config flag | Yes — documents intent for FlexField / config |
| Separate Spatie package dependency in this package | No — optional composer require spatie/laravel-translatable |
| Per-locale slug | No — one slug by design |
| Spatie locale fallback in form tabs | No — each tab shows that locale only |
Deep runtime bridge like laravel-sluggable | No — compatible state shape + hydrate/save, not a full plugin adapter |
spatieModel≠ Spatie Translatable.spatieModelonTitleSlugFieldis for Spatie Sluggable (HasSlug). For translations usetranslatableLocales(+ optionalspatieTranslatable).
Basic usage
slugSourceLocale tab drives permalink generation.
Storage without Spatie (recommended minimum)
title.pl / title.en into a title array. No extra glue code required.
Storage with Spatie laravel-translatable (optional)
HasTranslations and the package is installed, each tab is hydrated via getTranslation(). Otherwise tabs read the raw JSON attribute.
On save: the nested title array from the form is assigned to the model; Spatie JSON-encodes translatable attributes automatically.
Global defaults (config/filament-flex-fields.php)
translatableLocales is omitted in TitleSlugField::make(), locales are read from config('filament-flex-fields.slug.translatable_locales').
Required title locales
By default only the slug source locale title is required. Optional locales can stay empty on create/edit.required_title_locales (null, 'all', or list of locale codes).
Per-locale title customization
Full TranslatableFields passthrough
When translatableLocales is set, title tabs are built with TranslatableFields internally. By default the factory enables directionByLocale() and emptyBadgeWhenAllFieldsAreEmpty() (warning empty badge on tabs where the title is blank). The active tab stays on slugSourceLocale, not activeTabWithValue().
Use translatableFieldsConfigurator for further tweaks (activeTabWithValue(), bordered panels, custom tab icons, localeFieldUsing(), storageAttributeUsing(), etc.):
getSourceStatePath() resolves to title.{slugSourceLocale} automatically (e.g. title.pl).
SlugField translatable API
| Method | Description |
|---|---|
translatableTitle(bool|Closure $condition = true) | Enable translatable source resolution |
titleLocales(array|Closure $locales) | Locale map or list — also enables translatable mode |
slugSourceLocale(string|Closure $locale) | Locale used for slug generation |
translatableTitleField(string|Closure $fieldName) | Base title attribute name (default: title) |
spatieTranslatable(bool|Closure $condition = true) | Config flag (FlexField schema); hydrate auto-detects Spatie when present |
usesTranslatableTitle() | Whether translatable mode is active |
getTitleLocales() | Resolved locale => label map |
getSlugSourceLocale() | Effective source locale |
shouldUseSpatieTranslatable() | Evaluated spatieTranslatable flag |
FlexField schema config
| Config key | Maps to |
|---|---|
translatable_locales | TitleSlugField::make(translatableLocales: …) / titleLocales() |
slug_source_locale | slugSourceLocale() |
required_title_locales | requiredTitleLocales / TitleSlugField::make(requiredTitleLocales: …) |
spatie_translatable | spatieTranslatable() |
translatable_title_field | translatableTitleField() |
Slug generation locale
Translatable titles always use server-side slug preview (generateSlugPreview / Str::slug with slugSourceLocale). Alpine receives serverGenerate: true and slugSourceLocale so live preview matches PHP — including Polish diacritics (Łódź → lodz), which generic browser ASCII folding cannot handle reliably.
Spatie laravel-sluggable integration (v4.x)
Not required. If the default path without Spatie is sufficient, you can skip this section entirely.Tested with
spatie/laravel-sluggable ^4.0 (currently 4.0.2). The integration uses official Spatie v4 classes:
Spatie\Sluggable\Actions\GenerateSlugAction(viaconfig/sluggable.php→actions.generate_slug)Spatie\Sluggable\Support\SluggableAttributeResolverdla modeli z#[Sluggable]bezgetSlugOptions()Spatie\Sluggable\Support\Config::getAction()— ten sam resolver akcji co traitHasSlug
preventOverwrite). The form can display the same preview as the model — just pass spatieModel.
Optional dependency isolation (technical)
| Warstwa | Import Spatie\…? | Bez composer require spatie/laravel-sluggable |
|---|---|---|
SlugField, TitleSlugField | No | Works 100% (SlugGenerator, permalink, unique, inline edit) |
Traits GeneratesSlugFromSource, ConfiguresSlugPermalink | No — only SpatieSlugIntegration::isAvailable() | Guard before each Spatie call |
Support/Slug/SpatieSlugIntegration.php | Yes — the only slug bridge | Loaded safely; isAvailable() returns false, falls back to SlugGenerator |
spatie/laravel-sluggable jest w composer.json → suggest, nie w require. Pakiet nie wymusza instalacji Spatie.
Optional. Install when you want model-driven slug rules:
Minimal model (trait — klasycznie)
Minimal model (v4 attribute — bez getSlugOptions())
Wire the form (zero extra config)
Spatie in the form is enabled only when:- podasz
spatieModel, i - model ma
getSlugOptions()lub atrybut#[Sluggable]z rozpoznawalnymi opcjami.
Post as the Resource model does not enable Spatie automatically — the slug configuration must exist on the model.
spatieModel:
| Aspekt | Bez Spatie | Ze spatieModel |
|---|---|---|
UI Layout (FusedGroup) | Same | Same |
| Preview generation | Str::slug in JS or SlugGenerator | GenerateSlugAction + SlugOptions |
| Requesty Livewire | Opcjonalne | Tak (generateSlugPreview) |
| Zapis do bazy | Twoja logika / Filament | Spatie HasSlug na modelu |
SlugField:
Explicit Spatie field mapping
When form field names differ from model attributes:Supported Spatie SlugOptions features in preview
| Spatie option | Supported in live preview |
|---|---|
generateSlugsFrom('title') | Yes |
generateSlugsFrom(['title', 'subtitle']) | Yes — reads sibling form fields |
generateSlugsFrom(fn ($model) => ...) | Yes — Closure receives hydrated model |
saveSlugsTo() | Yes — via spatieSlugField() |
usingSeparator() / usingLanguage() | Yes |
slugsShouldBeNoLongerThan() | Yes |
generateUniqueSlugs / suffix | Yes — queries DB for collisions |
extraScope() | Yes — hydrates model from full form state (data.*, hidden fields, fillable attributes) |
startSlugSuffixFrom() / useSuffixOnFirstOccurrence() | Yes |
usingSuffixGenerator() | Yes |
skipGenerateWhen() | Yes — keeps existing slug in preview; reads hydrated form state |
preventOverwrite() | Yes — keeps existing slug in preview |
#[Sluggable] attribute (without getSlugOptions()) | Yes — via SluggableAttributeResolver (v4) |
#[Sluggable(from: ['title', 'subtitle']) | Yes — sibling form fields |
HasTranslatableSlug + spatie/laravel-translatable | Yes — preview for slugSourceLocale / app.locale |
selfHealing() / route keys | Yes — permalink preview and Visit URL append {slug}{separator}{id} on edit |
doNotGenerateSlugsOnCreate() / OnUpdate() | Model save only — preview always generates |
Custom GenerateSlugAction | Yes — via config/sluggable.php |
Multi-field source example
extraScope / skipGenerateWhen (e.g., tenant_id, status) filled out — SlugField reads them from the live form state (data.*), not just the source fields.
Scoped unique slugs (Spatie extraScope)
Attribute-based model (Spatie v4+)
getSlugOptions() required — SlugField reads the attribute when the method is absent.
Override Spatie for preview only
slugifyUsing() always wins over Spatie.
Force server-side preview
Spatie mode and translatable titles already use server-sidegenerateSlugPreview. For custom slugifiers or other cases:
Custom Spatie action class
If you overrideconfig/sluggable.php → actions.generate_slug, the field uses your bound GenerateSlugAction implementation automatically.
skipGenerateWhen — preview without overwriting
skipGenerateWhen returns true, the field will keep the existing slug instead of generating a new one.
startSlugSuffixFrom and collisions in preview
my-post already exists in the database, the preview may show my-post-5 (according to Spatie rules).
Closure as slug source
title and edition fields filled out — SlugField reads them from the live state of siblings in the schema.
usingSuffixGenerator — custom suffix
Permalink preview & URL actions
The permalink bar shows host (withouthttps://), optional path prefix, slug segment, and optional postfix. HTTPS hosts display a green lock icon.
Basic blog permalink
Subdomain style (host only)
Sandwich URL (prefix + slug + postfix)
Custom visit link (route or absolute URL)
visitUrl / visitRoute otrzymuje wstrzyknięte: slug (string), routeKey (string — dla self-healing modeli: hello-world-5, inaczej jak slug) i record (?Model).
Hide permalink or actions
Action button layout
Buttons sit below the input: Edit / OK / Cancel on the left; Regenerate / Copy / Visit on the right.config('filament-flex-fields.slug.action_button_labels').
Uniqueness validation
Separate from Spatie’s DB suffix generation — this is form validation before save.Default (unique in table)
Disable uniqueness check
Scoped uniqueness (tenant, locale, type, …)
Filament-style unique parameters
Homepage slug (/)
For CMS pages that should live at the site root:
/ is allowed as a special case.
TitleSlugField factory parameters
TitleSlugField::make() is a static factory returning a FusedGroup (title + hidden auto-sync flag + slug).
| Parameter | Type | Default | Description |
|---|---|---|---|
$fieldTitle | ?string | config('filament-flex-fields.slug.field_title') | Title state path |
$fieldSlug | ?string | config('filament-flex-fields.slug.field_slug') | Slug state path |
$titleField | ?Field | built-in FlexTextInput | Replace default title control |
$titleFieldWrapper | ?Closure | null | Wrap title field: fn ($field) => $field->columnSpan(2) |
$titleAfterStateUpdated | ?Closure | null | Hook after title changes |
$slugAfterStateUpdated | ?Closure | null | Hook after slug changes / regenerate |
$titleRules | array|Closure | ['required', 'string'] | Validation on title |
$slugRules | array|Closure | ['required', 'string'] | Validation on slug |
$titleAutofocus | bool | false | Focus title on create |
$titleReadOnly | bool|Closure | false | Read-only title |
$slugReadOnly | bool|Closure | false | Read-only slug |
$titleLabel | ?string | headline of field name | Title label |
$titlePlaceholder | ?string | headline of field name | Title placeholder |
$slugLabel | ?string | null (hidden) | Slug label; null hides it |
$titleExtraInputAttributes | array | [] | Extra HTML attributes on title input |
$slugUniqueParameters | ?array | null | Passed to slugUniqueParameters() |
$titleUniqueParameters | ?array | null | Filament unique() na tytule |
titleUniqueParameters — unique title within tenant:
$urlHost | ?string | config('filament-flex-fields.slug.url_host') | Permalink host |
| $urlPath | ?string | null | Permalink path prefix |
| $urlHostVisible | bool | true | Show host segment |
| $visitLinkLabel | ?string | translated default | Visit button label |
| $visitUrl | string\|Closure\|null | null | Custom visit URL; closure: slug, routeKey, record |
| $showVisitLink | bool | true | Show visit action |
| $slugLabelPostfix | ?string | null | Trailing URL segment after slug |
| $preserveSlugOnEdit | bool\|Closure | true | Don’t auto-update slug on edit |
| $translatableLocales | array\|Closure\|null | config('…slug.translatable_locales') | Enables TranslatableFields title UI; null = single-language title |
| $slugSourceLocale | string\|Closure\|null | config('…slug.slug_source_locale') or app.locale | Locale whose title drives slug generation |
| $requiredTitleLocales | 'all'\|list<string>\|Closure\|null | config('…slug.required_title_locales') or slug source locale only | Which title tabs are required (null = source locale only) |
| $spatieTranslatable | bool\|Closure | false | Marks Spatie Translatable intent; hydrate auto-detects HasTranslations when package is present |
| $titleLocaleConfigurator | ?Closure | null | fn (FlexTextInput $field, string $locale) => $field |
| $translatableFieldsConfigurator | ?Closure | null | fn (TranslatableFields $fields) => $fields->… — full title tabs config |
| $spatieModel | string\|Closure\|null | null | Spatie Sluggable only (HasSlug) — not Translatable |
| $slugConfigurator | ?Closure | null | fn (SlugField $field) => $field->... |
Example — custom title field + slug configurator:
Example for each parameter of TitleSlugField::make()
SlugField — configuration API
Each method below is chainable onSlugField::make('slug').
source(string|Closure|null $statePath)
State path of the field that drives auto-generation (usually title).
sourceLive(bool|Closure $condition = true)
When false, slug does not react to source changes (manual slug only).
translatableTitle(bool|Closure $condition = true)
Enables translatable title source paths (title.pl, …). Usually set via titleLocales().
titleLocales(array|Closure $locales)
Locale map (['pl' => 'PL', 'en' => 'EN']) or list (['pl', 'en']). Implies translatableTitle(true).
slugSourceLocale(string|Closure $locale)
Which locale title drives slug auto-generation. Default: config slug_source_locale, then app.locale, then first locale.
translatableTitleField(string|Closure $fieldName)
Base title attribute when resolving source path (default: title).
spatieTranslatable(bool|Closure $condition = true)
Configuration flag for Spatie Translatable models. Hydration auto-detects HasTranslations on the record when spatie/laravel-translatable is installed — the flag does not need to be true for detection to work.
titleField(Field $field)
Attach a title field for SlugField::withTitle() / manual fused layouts.
titleFieldWrapper(?Closure $wrapper)
titleAfterStateUpdated(?Closure $callback)
slugAfterStateUpdated(?Closure $callback)
titleReadOnly(bool|Closure $condition = true) / slugReadOnly(bool|Closure $condition = true)
Blocks editing of the respective field. Works with TitleSlugField and manual SlugField::withTitle().
slugifyUsing(?Closure $callback)
Custom slugifier; receives ['source' => string].
spatieModel(string|Closure|null $modelClass)
Enable Spatie integration for preview generation.
spatieSlugField(string|Closure $attribute = 'slug')
Model attribute Spatie writes to / reads from.
spatieSourceField(string|Closure|null $field)
Primary model attribute for the live source string.
serverSideGeneration(bool|Closure $condition = true)
Use Livewire generateSlugPreview instead of client Str.slug. Automatically enabled when Spatie integration is active or when translatable titles are used.
slugSeparator(string|Closure $separator = '-')
Normalization separator (also used by fallback SlugGenerator). Default validation slugPattern is derived from this separator automatically.
maxSlugLength(int|Closure|null $length)
Max length for fallback generator; Spatie uses slugsShouldBeNoLongerThan from model.
urlHost(string|Closure|null $host) / urlPath(string|Closure|null $path)
Permalink segments. Host may include https://; display strips the scheme.
urlHostVisible(bool|Closure) / urlPathVisible(bool|Closure)
Controls which URL segments are visible in the permalink preview.
permalinkPreview(bool|Closure $condition = true)
Show or hide the entire permalink chrome.
permalinkLabel(string|Closure|null $label)
visitUrl(string|Closure|null $url) / visitRoute(string|Closure|null $route)
Target of the Visit button. The Closure receives injected parameters: slug, routeKey (self-healing: {slug}-{id}), and optionally the record.
visitLinkLabel(string|Closure|null $label)
showVisitLink(bool|Closure) / showCopyButton(bool|Closure) / showRegenerateButton(bool|Closure)
Action toggles below the slug. By default all are true (except Regenerate — visible only after manual slug edit).
actionButtonLabels(bool|Closure) / actionButtonsIconOnly(bool|Closure)
Kontrola tekstu na przyciskach akcji (Hero UI button-group + ikony Gravity).
autoUpdateDisabledField(string|Closure|null $field)
Hidden boolean field path tracking manual slug edits. TitleSlugField sets {slug}_auto_update_disabled automatically.
autoGenerate(bool|Closure $condition = true)
Main toggle for auto-generating slug from the source field.
preserveSlugOnEdit(bool|Closure $condition = true)
On the edit operation, it stops auto-sync from the title (protects published URL). On create, it always syncs.
inlineEditing(bool|Closure $condition = true)
When true (default): permalink preview + Edit/OK/Cancel/Reset buttons. When false: standard TextInput.
allowHomepageSlug(bool|Closure $condition = true)
Allows homepage slug / (CMS homepage). Requires custom validation pattern.
generationDebounce(int|Closure $milliseconds = 400)
Debounce before regenerating slug from title.
slugPattern(string|Closure $pattern) / regex(string|Closure|null $pattern)
Optional override. When omitted, pattern is auto-derived from slugSeparator() (and allowHomepageSlug() when enabled). regex() is an alias.
slugLabelPostfix(string|Closure|null $postfix)
Trailing path after slug in permalink preview.
recordSlug(string|Closure|null $slug)
Initial/stored slug for edit preview and visit link before state hydrates.
slugRules(array|Closure $rules)
Additional validation rules (besides built-in pattern and slugUnique).
slugUnique(bool|Closure $condition = true) / slugUniqueParameters(array $parameters) / slugUniqueScope(?Closure $scope) / slugUniqueModel(string|Closure|null $model)
Uniqueness validation in the form (independent of Spatie suffixes on save).
size(string|Closure $size) / variant(string|Closure $variant)
Size and visual variant of the field wrapper (Hero UI). Defaults from config('filament-flex-fields.ui').
config('filament-flex-fields.ui.slug_size'), slug_variant.
Inherited Filament Field API
SlugField inherits standard Filament methods — they work exactly the same as in other fields:
TitleSlugField configures title and slug separately — supply slug helper text via the slugConfigurator:
Public helper methods (views, tests, extensions)
Public methods used in Blade templates, tests, and custom field extensions:| Method | Returns | When to use |
|---|---|---|
getAlpineConfiguration() | array | Debug / custom Blade |
getUiLabels() | array | UI translations in tests |
getSourceStatePath() | ?string | Livewire path to source field |
getOperation() | string | create lub edit |
generateSlugFromSource(string $source) | string | Server-side slug generation logic |
generateSlugPreview(string $source) | string | Livewire component action endpoint |
normalizeSlug(string $value) | string | Slug normalization helper before database save |
getFullPermalinkUrl(?string $slug) | ?string | Full URL for Copy/Visit actions |
getDisplayUrlHost() | ?string | Domain host without protocol |
usesSpatieIntegration() | bool | Whether Spatie sluggable options are resolved |
getSpatieModelClass() | ?string | Resolved FQCN model class |
shouldUseServerSideGeneration() | bool | Whether Alpine triggers Livewire preview requests |
getWrapperClasses() | list<string> | Component CSS wrapper classes |
FlexField schema keys (FieldType::Slug)
When using FlexFieldFormBuilder:
| Config key | Maps to method |
|---|---|
source | source() |
url_host | urlHost() |
url_path | urlPath() |
debounce | generationDebounce() |
slug_unique | slugUnique() |
spatie_model | spatieModel() |
separator | slugSeparator() |
allow_homepage | allowHomepageSlug() |
preserve_on_edit | preserveSlugOnEdit() |
Advanced recipes
Repeater with per-row title + slug
sections.0.title → sections.0.slug).
Standalone slug (no title field)
Regenerate button behaviour
Regenerate only appears when auto-sync has been disabled by a manual slug edit (setting the hidden field{slug}_auto_update_disabled = true). During normal syncing, the button is hidden.
Note:SlugFielduseswire:ignoreon the Alpine fragment — changingdata.slugdirectly in tests might not reflect UI behavior. Test by updating the title or modifyingslug_auto_update_disabled.
Playground (Live Preview)
Enable the playground in.env:
SubNavigationPosition::Start).
- Root URL:
/admin/flex-fields-playground(redirects to first component) - Slug field page:
/admin/flex-fields-playground/slug-field - Routes are registered only when
FLEX_FIELDS_PLAYGROUND=true(orfilament-flex-fields.playground.enabledistrue).
Spatie is optional for every recipe below. All playground demos work with browserSource of truth:Str::slug()only. To align preview and save withlaravel-sluggable, see Optional Spatie upgrade (playground recipes) at the end of this section.
SlugFieldPlayground in the package (src/Support/Playground/SlugFieldPlayground.php). Default form state keys (slug__title, slug__standalone, …) live in SlugFieldPlayground::defaultState().
Playground recipes — 1:1 with SlugFieldPlayground
Full section schema (copy-paste ready):
Recipe 1 — Shared title source + slug field (slug__standalone)
A separate title field drives one or more slug fields on the same form. Playground reuses slug__title for recipes 1, 5, and 6.
| Demonstrates | SlugField + ->source() pointing at a sibling field (not embedded titleField()) |
| Cookbook | Four ways to add title + slug — pattern 4 |
| Spatie | Optional — ->spatieModel(Post::class) on SlugField |
Recipe 2 — Title + slug one-liner (TitleSlugField)
| Demonstrates | TitleSlugField fused group, permalink bar, default create/edit behaviour |
| Cookbook | Scenario 1 |
| Spatie | Optional — TitleSlugField::make(..., spatieModel: Post::class) |
Recipe 3 — Translatable title + single slug (slug__i18n_*)
'slug__i18n_title' => ['pl' => '…', 'en' => '…'], 'slug__i18n_slug' => 'przewodnik-po-morzu-srodziemnym'.
| Demonstrates | translatableLocales, slugSourceLocale, TranslatableFields tabs |
| Cookbook | Translatable titles (single slug) |
| Spatie | Optional — spatieTranslatable: true when using Spatie Translatable |
Recipe 4 — Title + slug pair (titleField() + recordSlug())
| Demonstrates | Embedded title via ->titleField(), ->recordSlug() for visit URL on edit |
| Cookbook | Four ways — pattern 3 |
| Spatie | Optional — ->spatieModel(Post::class) |
Recipe 5 — Permalink preview + Visit link (slug__permalink)
Uses the shared title field from recipe 1 (slug__title).
| Demonstrates | urlHost, urlPath, visitRoute, generationDebounce |
| Cookbook | Permalink preview & URL actions |
| Spatie | Optional — visit URL still works; preview uses Spatie rules when ->spatieModel() is set |
Recipe 6 — URL slug sandwich (slug__sandwich)
Uses the shared title field from recipe 1 (slug__title).
wyachts.test/books/my-slug/detail/
| Demonstrates | slugLabelPostfix, complex visitRoute |
| Cookbook | Sandwich URL |
| Spatie | Optional |
Recipe 7 — Read-only variants (grid)
| Demo | Method | Cookbook |
|---|---|---|
| Form readonly | readOnly() | Scenario 10 |
| Slug readonly | slugReadOnly() | Scenario 3 |
Homepage / | allowHomepageSlug() + slugPattern() | Scenario 7 |
Optional Spatie upgrade (playground recipes)
None of the playground recipes requirecomposer require spatie/laravel-sluggable. Add it only when you need model-level suffixes (-2), preventOverwrite, extraScope, or identical rules on save and in the form preview.
TitleSlugField recipes (2, 3):
SlugField recipes (1, 4–7):
laravel-sluggable integration (install, HasSlug, getSlugOptions(), #[Sluggable], translatable models).
Playground quick reference
| Playground label | State key(s) | Primary API |
|---|---|---|
| Title (source) | slug__title | FlexTextInput + live() |
| Slug | slug__standalone | SlugField + ->source('slug__title') |
| Title + slug (one-liner) | slug__one_liner_* | TitleSlugField::make(...) |
| Translatable title + slug | slug__i18n_* | translatableLocales + slugSourceLocale |
| Title + slug pair | slug__pair_* | ->titleField() + ->recordSlug() |
| Permalink preview | slug__permalink | visitRoute + generationDebounce |
| URL slug sandwich | slug__sandwich | slugLabelPostfix + visitRoute |
| Form readonly | slug__readonly | readOnly() |
| Slug readonly | slug__slug_readonly | slugReadOnly() |
| Homepage slug | slug__homepage | allowHomepageSlug() |
Translations
Publish translation files:lang/vendor/filament-flex-fields/{locale}/default.php.
UI Keys (slug.*):
| Key | EN (Default) | PL |
|---|---|---|
slug.placeholder | your-permalink-slug | twoj-adres-slug |
slug.permalink | Permalink | Bezposredni link |
slug.badge_auto | Auto | Auto |
slug.badge_custom | Custom | Reczny |
slug.edit | Edit | Edytuj |
slug.confirm | OK | OK |
slug.cancel | Cancel | Anuluj |
slug.reset | Reset | Przywroc |
slug.regenerate | Regenerate | Regeneruj |
slug.copy | Copy | Kopiuj |
slug.copied | Copied | Skopiowano |
slug.visit | Visit | Odwiedz |
slug.changed | Changed | Zmieniono |
validation.slug.*):
| Key | Description |
|---|---|
validation.slug.invalid | General slug validation error message |
validation.slug.pattern | Validation error message when slug pattern mismatch |
app.locale = pl) to load built-in translations if desired.
Troubleshooting
| Problem | Likely Cause | Solution |
|---|---|---|
| Slug does not update from title | Manual edit disabled auto-sync | Click Regenerate or set {slug}_auto_update_disabled to false |
| Slug changes on edit but shouldn’t | preserveSlugOnEdit(false) | Use default TitleSlugField::make() or set preserveSlugOnEdit(true) |
| Preview mismatch with saved slug (no Spatie) | Database duplicate | The form unique validation blocks save — change the slug |
| Preview suffix -2 (Spatie) | Database duplicate resolved by Spatie | Expected behavior — preview uses same model configuration as Spatie HasSlug |
| Spatie not working | Missing package dependency | composer require spatie/laravel-sluggable |
| Spatie not working | Model missing getSlugOptions() or Sluggable attribute | Add slug configuration to model or pass class to spatieModel(Post::class) |
usesSpatieIntegration() returns false | Form model doesn’t match class with SlugOptions | Pass model class explicitly: spatieModel(Post::class) |
| Missing permalink bar | url_host is null | Set APP_URL or ->urlHost(config('app.url')) |
Permalink truncates host (...) | UX edit mode limits space | Normal behavior — full URL is copied to clipboard and visited |
| Unique validation fails incorrectly | Missing tenant scope (multi-tenant) | ->slugUniqueScope(fn ($q) => $q->where('tenant_id', ...)) |
| Unique doesn’t work on mount | Model record not loaded yet | Package defers validation — ensure the resource binds the record |
slugifyUsing() not working | Closure doesn’t return string or missing source state | slugifyUsing() always takes precedence over Spatie generator |
Test ->set('data.slug') fails | wire:ignore + Alpine | Change data.title or set slug_auto_update_disabled flag |
Target [Model] is not instantiable | Closure maps to model type hint on mount | Use mixed $record or remove type hint in closure parameters |
| Regenerate button is hidden | Auto-sync is still active | Manually edit the slug first to show the button (or set update flag) |
Homepage / slug rejected | Default pattern without allowHomepage | Call ->allowHomepageSlug() (updates default pattern regex) or custom pattern |
| Repeater rows do not sync | Incorrect source field path | ->source('title') in the same schema row resolves relative paths automatically |
Comparison with blendbyte/filament-title-with-slug
| Feature | Flex Fields TitleSlugField | blendbyte |
|---|---|---|
| Title + slug fused layout | Yes | Yes |
| Permalink preview + HTTPS lock | Yes | Partial |
| Copy URL button | Yes | No |
| Regenerate after manual edit | Yes | Limited |
| Auto / Custom badge | Yes | No |
Standalone SlugField | Yes | No |
| Scoped unique validation | Yes | Basic |
Full Spatie SlugOptions preview | Yes | Partial |
#[Sluggable] attribute support | Yes | No |
Multi-source generateSlugsFrom([]) | Yes | No |
| Icon-only action buttons (Hero UI) | Yes | No |
| FlexField / playground integration | Yes | No |
| Gravity icons | Yes | Heroicons |