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.
Architecture fit
drobee/nova-sluggable is a deliberately narrow artifact: a single Nova form field that derives a slug from a source attribute and writes it into the resource form. It is a presentation-layer control surface, not a slug engine. That distinction is the whole architecture conversation — it does not touch models, migrations, API controllers, importers, seeders, or queue workers. Every non-admin write path remains the responsibility of a model-level trait (typically spatie/laravel-sluggable).
Fit is therefore conditional and cheap to assess:
Slug field with source syncing. Until a written acceptance criterion demonstrates native is insufficient, the architecturally correct answer is to add no dependency at all. This evaluation should not proceed past that gate.Integration feasibility
Low effort, low surface area, high reversibility. The integration is a composer require plus a Slug::make('Slug')->from('title') line on the target resources. The package is MIT and roughly a hundred lines of field code, so comprehension cost is an afternoon, not a sprint.
The real feasibility work is preconditions, not code:
composer install fails for this field and every other Nova field — a pre-existing condition this package will expose, not create.unique:table,slug,{{resourceId}} validation rule. The field cannot guarantee uniqueness; concurrent creates are a database problem.null while the editor's form works — the worst class of inconsistency, and one that surfaces as an SEO defect rather than an error.composer require. This is the single most common silent failure and costs minutes to check on a branch.Technical risk
| Risk | Likelihood | Impact | Mitigation |
|---|---|---|---|
| Compiled assets break on Nova upgrade | Medium-high | Blank slug input in admin, silent | CI smoke test rendering the resource form; pin narrow constraint |
| Scope misread as "slugs are handled" | High | Null/duplicate slugs from API, imports, seeders | Trait as source of truth; documented convention |
| Upstream goes dark (0 dependents, 45 stars) | Medium | Field rots until a form breaks | MIT fork into app/Nova/Fields, pre-agreed owner |
| Hand-curated slugs overwritten | Low-medium | Irreversible SEO regression | Back up column; gate write-back before enabling editable mode |
| Transliteration gaps (CJK, accented) | Medium if multilingual | Empty/malformed slugs | Test with real content; rely on trait transliteration |
| Update semantics surprise | High | Typo fix leaves stale slug; stakeholder confusion | Write the acceptance criterion down, verify field matches |
Note the release date (May 2024) and zero dependents: there is no community backstop, no "someone already filed this bug" safety net, and compatibility will not move in lockstep with Nova or Laravel majors.
Key questions
Slug field already satisfy the written acceptance criteria? If yes, terminate the evaluation here.Stack fit
Laravel + Nova, exclusively. The package is inert outside a Nova panel, so if the admin layer is Filament or bespoke Blade, the answer is a model trait plus a plain text field and this evaluation closes. Assuming Nova: the field composes naturally with the existing resource class, sits as a sibling of the source attribute so the generate action reads in context, and imposes no migration beyond the slug column the model already needs. Its only runtime coupling is to Nova's field asset pipeline.
Migration path
No data migration. The slug column either exists or is added with string('slug')->unique()->nullable(); existing rows are backfilled by the model trait, not by the field. This makes adoption and reversal symmetric — adding the field is a ->make() line, removing it is deleting that line, and no schema or persisted state depends on the package.
The one genuine migration decision is update semantics: does the slug regenerate when the title changes, or only on create? Most implementations sync on create. That behaviour must be stated as an acceptance criterion before rollout, because stakeholders will otherwise discover it as a bug ("I fixed the typo, the URL didn't change"). If any form of slug history or 301 redirect on change is desired, this is out of scope — that is a product feature with real scope, and the correct decision is to defer rather than stretch a form field.
Compatibility
Two version axes and both need verification on a branch:
Sequencing discipline: pin a narrow Composer constraint, and add a CI job that boots Nova and renders the resource form. That job is what converts a silently broken admin form into a build failure — the entire compatibility posture rests on it.
Sequencing
Slug field against written acceptance criteria. Most likely outcome: native wins and no dependency is added. Do this before anything else.Post resource, load the create form, confirm compiled assets render, test transliteration with real content, document actual fluent method names from vendor/ source (the README is thin).Standardise on one field type app-wide. Mixing Nova native on some resources and this package on others doubles the upgrade blast radius — two asset pipelines to debug on every Nova bump.
Maintenance
The dominant ongoing cost is not code, it is compatibility re-verification on every Nova and Laravel major. Budget one patch per major release, with a named maintainer. With zero dependents and 45 stars there is no upstream community pressure to fix issues, so the internal owner is the de facto maintainer — the fork is the plan, not the fallback. Because the surface is roughly a hundred lines and MIT-licensed, vendoring into app/Nova/Fields is a legitimate exit that costs hours, not weeks. Pre-agree that exit path and its owner before adoption.
Secondary maintenance: keeping the slug convention documented so the admin-form-only boundary is understood by the next engineer, and keeping editable vs. read-only policy consistent across resources.
Support
Support load should be near zero if the scope boundary is socialised. The failure it introduces is not a crash — it is a silent inconsistency between the admin form and every other write path. That class of issue arrives as "the URL is blank on the API-created article" with no error log to point at, which is expensive to diagnose and easy to prevent with the trait. Ticket deflection is the actual value: editors who currently file engineering tickets to fix a slug get a form field instead. That is a measurable reduction in low-value tickets, and the right success metric for the pilot.
Scaling
Scales fine within its envelope. Uniqueness enforcement is on the database index, so concurrent creates collide and retry at the database layer, not in the field. The field adds no per-request overhead worth modeling. The scaling limit is organisational, not technical: as the number of Nova resources grows, the number of ->make() sites that must be touched on a Nova major grows linearly. That is why standardising on one field type and pinning the constraint matters more at scale than any performance consideration.
Failure modes
{{resourceId}} validation rule.Ramp-up
Engineering: one engineer, half-day spike, no prior Nova-field expertise required — the source is short enough to read end to end, and that reading is the onboarding. Total adoption cost is roughly 1–3 dev-days including tests, offset against the same 1–3 days to build in-house, which is why the buy-vs-build question is largely a wash and the native-field comparison is the only decision that really matters.
Editorial/stakeholder: minimal, but two items need explicit communication before launch — (a) slugs are only auto-generated in the admin form, and (b) whether the slug changes when the title changes. Both are one-line statements that prevent a disproportionate share of downstream confusion.
Recommendation: a one-resource pilot behind a short spike, treated as a swappable convenience layer with slug logic owned by the application, not the package. Do not treat it as a platform commitment. The correct first action is proving Nova's native field insufficient; if it is sufficient, add nothing.
How can I help you explore Laravel packages today?