drobee/nova-sluggable
A Laravel Nova field for generating and managing URL slugs from a title or other attribute. Adds a sluggable input to Nova resources, creating unique, SEO-friendly slugs automatically and keeping them in sync as records are edited. A simple drop-in field for Eloquent models.
Before you touch Composer. This package is inert outside Laravel Nova. Confirm Nova is installed and licensed first, then pin down three things before composer require:
composer show laravel/nova). The May 2024 release most likely targets Nova 4. If you're on Nova 5, treat compatibility as unverified and test on a branch.Schema::table('posts', function (Blueprint $table) {
$table->string('slug')->unique()->nullable()->after('title');
});
spatie/laravel-sluggable is the common pairing:use Spatie\Sluggable\HasSlug;
use Spatie\Sluggable\SlugOptions;
class Post extends Model
{
use HasSlug;
public function getSlugOptions(): SlugOptions
{
return SlugOptions::create()
->generateSlugsFrom('title')
->saveSlugsTo('slug');
}
}
Install.
composer require drobee/nova-sluggable
Read the source before writing against it. The field class in vendor/drobee/nova-sluggable/src/ is small — a few dozen lines. The README is thin, so confirm the actual fluent method names (->from(), ->separator(), ->readonly(), update semantics) from the code rather than assuming they match the docs. Fifteen minutes here saves you a broken form later.
First use case: a Post resource. Register the field next to Title, branch it, load the create form, and verify the compiled JS actually renders and auto-fills as you type. If the input is blank, stop — do not build on top of a broken asset pipeline.
Basic registration. Put the slug field as a sibling of the source attribute so the generate action lives in context:
use Drobee\NovaSluggable\Slug;
public function fields(Request $request)
{
return [
ID::make()->sortable(),
Text::make('Title')->rules('required', 'max:255'),
Slug::make('Slug')
->from('title')
->separator('-')
->rules('required', 'max:255', 'unique:posts,slug,{{resourceId}}'),
];
}
The unique rule with {{resourceId}} is what prevents the duplicate-on-update failure that slug fields commonly trip over. The field itself won't enforce it.
Read-only vs editable. Default to read-only for editors who shouldn't own URLs; expose editing only where regeneration is genuinely desired:
Slug::make('Slug')->from('title')->readonly(), // auto-generate, no hand-editing
Slug::make('Slug')->from('title'), // editable + regenerate
Always pair with the model trait. The field gives editors a control; the trait guarantees that API writes, imports, and Model::create() calls still produce a slug. Without the trait you get a silent inconsistency — editor forms work, API endpoints write null — which is the worst failure mode because it surfaces in production, not in the admin.
Standardise one field type app-wide. If you adopt this, use it everywhere you need slug UX. Mixing Nova's native Slug field on some resources and this package on others doubles the asset pipeline you'll debug on every Nova bump.
Decide update semantics explicitly. Most implementations sync on create only. Stakeholders will be surprised when a title typo fix leaves the old slug (and potentially a broken public URL) in place. Write the acceptance criterion down, then verify the field's behavior matches it — don't infer from the README.
Test the render, not just the install. A Nova endpoint smoke test catches asset regressions on upgrade:
public function test_post_slug_field_renders()
{
$this->actingAs($this->admin())
->get('/nova-api/posts/fields')
->assertOk();
}
Also test transliteration with real content — an accented or CJK title — before assuming the field handles it.
composer require succeeded. Test the rendered form as part of installation acceptance — it's the cheapest check and the most common reason for a silent failure.unique:...,{{resourceId}} rule. Never rely on the field alone; concurrent creates will collide.slug column and gate the write-back before enabling editable mode — an SEO regression is expensive to unwind.composer install fails on this field — and every other Nova field — before any test runs.Slug field with source syncing. If it already covers your requirements, the correct call is not adding this dependency. Do the comparison before the spike, not after.app/Nova/Fields is fully reasonable if upstream goes quiet or a Nova 5 patch never lands. Pre-agree the fork owner and the removal path (delete package, strip ->make() calls) before committing.How can I help you explore Laravel packages today?