Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Slug Type Bundle Laravel Package

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.

View on GitHub
Deep Wiki
Context7

Getting Started

This is a Symfony bundle, not a Laravel package — there is no service provider, config publish tag, or Slug facade. If you're coming from Laravel, treat it as a reference implementation you can either (a) use inside a Symfony app, or (b) copy the ~100-line JS + form-type pattern into a Laravel + Alpine/Stimulus setup.

Minimal steps in a Symfony app:

  1. composer require leapt/slug-type-bundle:^1.5 (Symfony Flex will usually register it in config/bundles.php; otherwise add Leapt\SlugTypeBundle\LeaptSlugTypeBundle::class => ['all' => true]).
  2. Install the bundle assets so the JS is reachable: php bin/console assets:install public (or import the shipped JS into your Encore/AssetMapper pipeline if you build assets).
  3. Include the bundle's JS in the layout that renders the form — this is the step people forget; without it the field renders fine but never auto-fills.
  4. Add the type to a form:
use Leapt\SlugTypeBundle\Form\Type\SlugType;

$builder
    ->add('title', TextType::class)
    ->add('slug', SlugType::class, [
        'target' => 'title', // property name of the source field
    ]);

Version note (v1.5.0): the only functional change is a widened symfony/stimulus-bundle constraint (now allows v3) plus CI/workflow housekeeping. No BC breaks, no new form options, no config changes — upgrade freely. The practical effect: if your app is on Symfony UX / stimulus-bundle 3.x, v1.4.x would have blocked composer update; v1.5.0 unblocks it. The mention of Stimulus is a strong hint the shipped JS is (or can be) registered as a Stimulus controller — check src/Resources/assets/ before writing your own re-init glue.

First real use case: an admin/article form where editors type a title and the slug populates live underneath it, but they can still override it before saving. Look first at src/Form/Type/SlugType.php and the shipped JS file — the whole surface area is tiny, so reading the source is faster and more reliable than the README.

Implementation Patterns

Always pair the widget with a server-side slug fallback. The auto-fill is pure JS (it reads the target input and writes a slugified value into the slug input on keyup/input). Anything that can bypass the DOM — API clients, curl, tests, disabled JS — will submit an empty slug. Do the real work in your entity setter or a PRE_SUBMIT form listener:

use Symfony\Component\String\Slugger\AsciiSlugger;

public function setTitle(string $title): self
{
    $this->title = $title;
    if (!$this->slug) {
        $this->slug = strtolower((new AsciiSlugger())->slug($title));
    }
    return $this;
}

Only fill when the slug is untouched. Good UX pattern is: keep the generated value until the user manually edits the slug field, then stop overwriting. The bundle's JS typically respects this, but verify — otherwise every keystroke in the title clobbers a hand-curated slug.

Reuse via a base form type. If you have many entities with slug fields, create an abstract SluggableFormType that adds title/slug and let concrete forms extend it. This keeps the target and type options in one place.

EasyAdmin / Sonata integration. Both can render arbitrary Symfony form types, so you get the widget "for free" by naming SlugType::class in the field config — but check that the admin's form theme doesn't strip the data-* attributes the JS depends on (see Gotchas).

Asset pipeline choice. With Webpack Encore, import the bundle's JS from your app.js. With AssetMapper, importmap or a simple asset() reference to the installed public path. If the bundle exposes a Stimulus controller (v1.5.0's constraint bump points that way), prefer {{ stimulus_controller(...) }} / bootstrap.js registration over hand-rolled listeners — you inherit the connect/disconnect lifecycle for free. Pick one approach and document it — mixing causes "works on my machine" bugs.

Turbo / Stimulus / Live Components. If you use Hotwired Turbo (or Symfony UX Live Components), a page transition re-renders the form without a full reload, and ad-hoc DOM listeners won't be re-attached. Two options: register the bundle's controller via stimulus-bundle (auto-connects on Turbo navigation — the low-effort path on v1.5.0), or re-initialize manually on turbo:load / live_component:render.

Porting the pattern to Laravel. The logic is trivial to replicate: an Alpine x-on:input on the title that calls a Str::slug()-ish JS function and writes into the slug input, gated by a slugDirty flag. Don't try to composer require this into a Laravel app — it will pull Symfony Form/OptionsResolver and nothing will wire up.

Gotchas and Tips

  • Symfony-only, zero Laravel dependents. Package stars/dependents are near zero (9 stars, 0 dependents), and the release cadence is sparse (v1.5.0 is a dependency bump, not a feature release). The code is small enough to vendor or copy outright; don't bet a critical feature on upstream fixes.
  • Stimulus version conflicts after upgrading. v1.5.0 exists precisely because v1.4.x pinned stimulus-bundle below v3. If you have a locked composer.lock from an older app, run composer update leapt/slug-type-bundle symfony/stimulus-bundle together — updating only one can leave you in a constraint deadlock.
  • Silent failure without the asset include. The field renders perfectly even if the JS never loads — no console error, just a slug that never populates. If "auto-fill isn't working," check the Network tab for the bundle JS first.
  • target is the field name, not the label. If you use nested forms or property_path, the target must match the child field name the JS queries by id/name. Rename a field and the JS silently targets nothing.
  • Custom form themes can drop the data attributes. The wiring relies on data-* attributes emitted by the bundle's widget. If you override form_widget for text types — including via EasyAdmin/Sonata field templates — you can lose them and break the JS.
  • JS vs PHP slugification diverge. The client-side slugify and AsciiSlugger/cocur/slugify handle accents, CJK, and separators differently. Users will occasionally see one value in the input and a different one after save. Normalize in one place (server) and treat the JS as cosmetic.
  • Double-binding if you mix Stimulus with a manual turbo:load hook. If you register the shipped Stimulus controller and keep a legacy listener, the slug field can be updated twice or fight the dirty flag. Pick one mechanism.
  • No config quirks to speak of. There's no bundle config tree; behavior is controlled entirely via form options (target, and whatever the type exposes). That's a feature: nothing to misconfigure.
  • Extension points: override the form theme to change markup, replace the shipped JS with your own controller on the same data attributes, or subclass SlugType to bake in defaults. All three are low-risk because the public API is a single form type and a couple of options.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings
coleus/apps
seampact/laravel
emmyconceptng/laravel-ng-postcode
jjsquady/round-robin
skyraptor/chatter
edlugz/sasapay
abbasudo/artisan-route