leapt/slug-type-bundle
Symfony bundle that adds a reusable SlugType form field for generating URL-friendly slugs from user input. It streamlines slug creation and validation in forms, helping keep routes and content identifiers clean, readable, and consistent across your application with minimal setup.
Architecture fit
The package is a Symfony bundle, not a Laravel package, and this is the single most important fact for the evaluation. leapt/slug-type-bundle provides a Symfony Form Type (AbstractType subclass) that wires a form field to auto-populate from a sibling text field, plus the matching front-end behavior. Laravel does not ship the Symfony Form component, does not have a bundle registration mechanism (no services.yaml, no DependencyInjection/Extension), and does not consume Symfony's AssetMapper/Encore asset conventions. On a Laravel codebase the PHP half is effectively dead weight — it cannot be installed to useful effect.
What is architecturally relevant is the behavioral pattern: a client-side listener on a source field that slugifies into a target field, with sensible guards (don't overwrite a user-edited slug, handle empty/disabled states). That pattern is framework-agnostic and is the actual thing worth harvesting.
Integration feasibility
symfony/form), but nothing in Laravel will invoke the form type.spatie/laravel-sluggable, Str::slug) and the front-end behavior is commodity. The build-vs-buy calculus strongly favors building.Technical risk
Str::slug() or your existing DB constraints — silent divergence between client-generated and server-generated slugs is a real correctness bug class.Key questions to answer before committing
Str::slug()?composer.json require, and is any of it in conflict with a modern Laravel dependency tree?Stack fit Assume Laravel 10/11/12 with Blade, plus either Livewire, Inertia+Vue/React, or plain server-rendered forms. The Symfony Form Type has no seat at this table. The correct framing is: this package is a design reference for a small UI behavior, not a library to install. Record that conclusion in the ADR so the question doesn't get re-litigated.
Migration path
Str::slug() at save time, or spatie/laravel-sluggable), with the client-side generator purely as UX sugar that pre-fills the field. This avoids trusting the browser for a persisted identifier.<x-slug-field :source="title" name="slug">) plus an Alpine.js snippet, or the equivalent composable in Vue/React. Keep it in your own repo — a ~40-line component you own beats a dependency you don't.-2, -3) — the client cannot be responsible for this.Compatibility No compatibility surface exists because nothing is installed. The only compatibility question is whether your slug output matches what the bundle would have produced — decide this deliberately and pin it in a test, because existing rows and any imported data must agree with new rows.
Sequencing Spike → ADR (build vs. adopt, authority model) → front-end component → server-side slug + uniqueness → tests (empty source, manual edit, unicode input, duplicate) → ship behind a flag on one form → roll out. Total realistic effort: 2–4 developer days, most of it on uniqueness and tests rather than the auto-fill itself.
Maintenance Near-zero ongoing cost, and that is the strongest argument for the port: you own ~40 lines of JS instead of tracking a Symfony bundle's releases, Symfony major-version bumps, and asset-pipeline migrations. If instead you fork the bundle to keep it "close to upstream," you have taken on Symfony release-tracking overhead for a feature that does not need it. Do not fork.
Support No vendor, no commercial backing, no community. Any defect is yours. The only thing you get from upstream is the initial reading — budget for it to be stale within a year and plan to treat your local implementation as the source of truth.
Scaling Runtime cost is negligible — it is client-side DOM work on a single form. The scaling risk is data, not compute: concurrent form submissions and non-Latin titles both push toward empty or duplicate slugs. Mitigate with a NOT NULL constraint, a unique index, and a server-side fallback slugifier. At high write volume the collision-retry path needs to be a bounded loop, not an unbounded one.
Failure modes
Ramp-up Trivial — any Laravel developer understands auto-slug fields intuitively and can read the ported component in minutes. The non-obvious parts (dirty-latch semantics, transliteration parity, collision handling) should be captured in the ADR and a handful of feature tests; that documentation is worth more than the source package. Do not send anyone to the upstream repo expecting onboarding material — with 9 stars and no dependents, the docs are thin and unvalidated.
Verdict: Adopt the pattern, not the package. The bundle answers "how should this interaction behave" and does so competently; it does not answer "how do I ship this in Laravel." Treat it as a half-day reference read and move on.
How can I help you explore Laravel packages today?