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

Laravel Sluggable Laravel Package

caturseptian/laravel-sluggable

A Laravel package for automatically generating and managing SEO-friendly slugs for Eloquent models. Customize slug sources, separators, and uniqueness rules to keep URLs clean, readable, and consistent across your application.

View on GitHub
Deep Wiki
Context7

Getting Started

This package adds automatic, unique slug generation to Eloquent models. The minimum viable setup:

  1. Install and publish

    composer require <vendor>/<package>
    php artisan vendor:publish --tag="sluggable-config"
    

    If your application does not auto-discover the service provider (or you want the package's migration/config copied into app/ rather than referenced from vendor/), use the ejection command instead:

    php artisan sluggable:eject
    

    As of v1.0.2 this command is atomic: every file is written to a temporary location first and only moved into place once all writes succeed. A failed write leaves your app untouched rather than half-ejected. This matters if you run it in CI or inside a deploy script — you can now trust the "all or nothing" semantics.

  2. Add the trait to a model

    use HasSlug;
    
    public function getSlugOptions(): SlugOptions
    {
        return SlugOptions::create()
            ->generateSlugsFrom('title')
            ->saveSlugsTo('slug');
    }
    
  3. Look here first: config/sluggable.php for the global defaults (slugsShouldBeNoLongerThan, separator, suffix handling), then the trait on your model. Those two files answer 90% of "why is my slug wrong" questions.

  4. First use case: a Post model with a title. Create three posts titled "Hello World" and confirm you get hello-world, hello-world-1, hello-world-2. That single test exercises generation, uniqueness, suffixing, and the length cap all at once.

Implementation Patterns

Centralise options in one method. Put getSlugOptions() on a shared base model or trait so every sluggable model shares the same separator/casing/length policy. Divergent per-model config is the most common source of inconsistent URLs in a codebase.

Treat the slug column as immutable-by-default. Decide up front whether slugs regenerate on update. If URLs are public and shared (SEO, links in emails), regenerate only when the slug column is empty; otherwise you break inbound links. Use the package's source-immutability option rather than guarding in your controller.

Uniqueness scope. For multi-tenant apps, uniqueness usually needs to be per-tenant, not global. If the package exposes a scope callback for the uniqueness query, wire it to your tenant column; otherwise generate the base slug manually and append a tenant-aware suffix. Test this explicitly — global uniqueness silently "works" until two tenants pick the same title and the second gets an unexpected -3.

Length budget. slugsShouldBeNoLongerThan is your guardrail against DB column overflow and ugly URLs. Set it in config to something comfortably below your column length (e.g. 80 for a 100-char column) so the suffix always fits. Combine it with a suffix scheme whose maximum expected width you control.

Queue/import paths. When bulk-importing, slugs are generated on each save(). For large imports, batch and wrap in a transaction, and be aware that unique-slug generation does a lookup per row — it is not free at 100k rows. Consider pre-computing slugs for imports.

Ejecting as a fork point. sluggable:eject is the supported way to take ownership of the package's config/migration/service-provider wiring. Use it when you need to diverge (custom migration index, extra config), and commit the ejected files so the shape of your fork is reviewable.

Gotchas and Tips

Breaking behaviour in v1.0.2 — short length caps now throw. Previously, if slugsShouldBeNoLongerThan was shorter than the uniqueness suffix, the generated slug would silently exceed the limit (limit 1 with suffix -1 produced a 2-character slug, ignoring your configuration). This now raises InvalidOption. Audit any model or config where the cap is very small — anything under roughly 3–5 characters is now a hard failure. The thrown exception is the correct outcome, but it will surface at runtime on save(), so add a test that asserts your minimum cap is greater than your longest plausible suffix. Pick a cap that leaves room for at least -999.

Catch it at boot, not at save. Because the error fires during model save, a bad cap in a rarely-used model can sit dormant until a user hits an edge case. If you have many sluggable models, add a cheap test or a config assertion that walks your models and validates each SlugOptions against the configured cap.

Eject temp-directory permissions. The new atomic eject writes to a temp location before moving. If your deploy user lacks write access to the system temp dir or the target directory, you now get a clean failure — good — but the error is about the temp path, which can be confusing. Check sys_get_temp_dir() first when debugging a failed eject.

Re-running eject. Atomicity does not mean idempotent. Confirm whether a second run overwrites your local modifications or skips them; if it overwrites, treat ejected files as generated output and keep your customisations in a separate layer, or remove the eject step from routine deploys and run it only once.

Length is characters, not bytes. Multibyte titles (CJK, emoji) count differently than your DB column if the column is VARCHAR(n) measured in bytes on some setups. Verify with a multibyte title that the generated slug both respects slugsShouldBeNoLongerThan and fits the column.

Config caching. Run php artisan config:clear after changing the length cap. A cached config in a long-running worker will keep generating slugs under the old rules, and with the v1.0.2 change you may see the InvalidOption from a stale process while the code looks correct.

Extension points to reach for before forking. Prefer the package's option builders over editing ejected source: regeneration conditions, source field(s), separator, and uniqueness scoping. Fork via eject only when the option surface genuinely cannot express your need — otherwise you inherit the maintenance burden of tracking upstream fixes like this one.

Upgrade note. v1.0.2 is a small, safe patch, but the length/suffix validation change is a behaviour change for anyone with tight caps. Pin the version, upgrade in a branch, run your model/slug tests, and check logs for InvalidOption on staging before rolling out.

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.
abbasudo/artisan-route
mirvan/ippanel
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
adachsoft/api-version-bundle
cgv/localized-resource
noith/settings
kda/laravel-package