Kokil Thapa - Professional Web Developer in Nepal
Freelancer Web Developer in Nepal with 15+ Years of Experience

Kokil Thapa is an experienced full-stack web developer focused on building fast, secure, and scalable web applications. He helps businesses and individuals create SEO-friendly, user-focused digital platforms designed for long-term growth.

Laravel URL Slugs with Sluggable Package

By Kokil Thapa | Last reviewed: September 2026

Readable URLs are not a cosmetic detail on production Laravel applications—they affect crawlability, click-through rates, and how confidently you can share links in emails or social posts. Laravel URL Slugs with Sluggable Package automation saves you from hand-maintaining kebab-case strings every time an editor renames a blog post, product, or legal guide page. On law-firm portals and eCommerce builds I maintain, slug generation belongs in the model layer, not scattered across controllers or admin forms. If you are shipping on Laravel web development in Nepal or globally, this guide walks through the package most teams reach for first—cviebrock/eloquent-sluggable—with routing, uniqueness, SEO wiring, and Nepali-script edge cases that actually show up after launch.

Why does Laravel URL Slugs with Sluggable Package matter for SEO and routing?

A slug is the human-readable segment at the end of a URL: /court-marriage-requirements-nepal instead of /posts/847. Search engines treat descriptive paths as a weak relevance signal, but the bigger win is operational—editors understand URLs, analytics reports become readable, and Laravel SEO setup stays consistent across sitemaps, breadcrumbs, and Open Graph tags.

Manual slug fields in admin panels break down quickly. Someone duplicates a title, pastes uppercase text, or leaves the slug empty. A sluggable package generates the value on create (and optionally on update), applies consistent rules, and can append numeric suffixes when two records share the same title. That pattern appears constantly on content-heavy sites—legal guides on Court Marriage In Nepal, directory listings, and product catalogues on Nepal Gift Card.

Slug Generation PipelineTitle FieldUser inputSluggableTrait methodSlug ColumnDB uniqueRoute Binding{model:slug}Public SEO URL/guides/court-marriage-nepalSEO LayerCanonical tagSitemap entrySchema @id URLInternal link targets
Laravel URL Slugs with Sluggable Package: title input flows through the trait into a unique database column, then into route binding and SEO metadata.

Google’s own URL structure guidance recommends simple, descriptive paths with readable words rather than opaque IDs. Laravel gives you explicit route model binding; the sluggable package gives you a stable lookup key. Together they replace brittle where id = ? public URLs without sacrificing database primary keys internally.

How do you install and configure Eloquent Sluggable in Laravel 13?

Start with PHP 8.3 or higher and Laravel 13.x. The widely deployed package is cviebrock/eloquent-sluggable—maintained specifically for Eloquent lifecycle hooks, not a generic string helper. Install via Composer 2.10:

composer require cviebrock/eloquent-sluggable

Migration and model setup

Add an indexed slug column. For high-traffic tables, a unique index prevents race-condition duplicates at the database level:

Schema::create('articles', function (Blueprint $table) {
    $table->id();
    $table->string('title');
    $table->string('slug')->unique();
    $table->text('body');
    $table->timestamps();
});

Follow the same discipline you would on any production schema—see database migrations and seeding best practices in Laravel for rollback-safe patterns. Wire the trait into your model:

<?php

namespace App\Models;

use Cviebrock\EloquentSluggable\Sluggable;
use Illuminate\Database\Eloquent\Model;

class Article extends Model
{
    use Sluggable;

    protected $fillable = ['title', 'body'];

    public function sluggable(): array
    {
        return [
            'slug' => [
                'source' => 'title',
                'onUpdate' => false,
                'unique' => true,
            ],
        ];
    }
}

Route model binding by slug

Laravel 13 supports scoped binding directly in the route definition—cleaner than overriding getRouteKeyName() globally:

Route::get('/articles/{article:slug}', [ArticleController::class, 'show'])
    ->name('articles.show');

Controller type-hinting stays unchanged. Laravel resolves the model by the slug column because you specified the binding key. Official details live in the Laravel route model binding documentation.

  1. Install cviebrock/eloquent-sluggable with Composer.
  2. Add a unique slug column in your migration.
  3. Apply the Sluggable trait and define sluggable() with a source field.
  4. Bind routes with {model:slug} instead of numeric IDs.
  5. Emit canonical URLs and sitemap entries using the slug-based named route.

On greenfield apps I align this with modern Laravel architecture—keep slug logic in the model, validation in Form Requests, and SEO metadata in a dedicated layer such as artesaos/seotools, which I use regularly alongside sluggable on content sites.

How do you handle duplicate slugs and uniqueness in Laravel?

Two articles titled “Divorce Process in Nepal” will collide without a strategy. The package’s unique => true option queries existing rows and appends -1, -2, and so on. That is usually enough for blogs and legal guides on platforms like Notary Nepal.

public function sluggable(): array
{
    return [
        'slug' => [
            'source' => 'title',
            'unique' => true,
            'uniqueSuffix' => '{%n}',
            'separator' => '-',
            'maxLength' => 80,
            'maxLengthKeepWords' => true,
        ],
    ];
}

Scoped uniqueness

Multi-tenant or category-scoped content needs uniqueness within a scope, not globally. Pass a closure:

'unique' => true,
'uniqueSuffix' => '{%n}',
'includeTrashed' => false,
'scope' => fn ($query, $model) => $query->where('category_id', $model->category_id),

That allows identical titles in different categories—useful for directory sites such as Lawyers Pokhara where “Family Law” pages repeat across regions.

Duplicate Slug ResolutionNew: divorce-nepalCheck DB uniqueExists?Query scopedSave slugAppend -1divorce-nepal-1301 Redirect old slugPreserve link equity after title edits
When Laravel URL Slugs with Sluggable Package detect a collision, numeric suffixes resolve duplicates; title changes need explicit redirect planning.

Should slugs update when the title changes?

Default behaviour keeps the original slug (onUpdate => false)—the right choice for published SEO URLs. Changing slugs without a 301 redirect breaks inbound links and Search Console history. If editors must rename URLs, store old_slugs in a JSON column or a dedicated redirects table and fire a redirect in middleware. Test edge cases with a regex tester when you validate slug patterns in Form Requests:

'slug' => ['nullable', 'regex:/^[a-z0-9]+(?:-[a-z0-9]+)*$/', 'max:80'],

Reserved slugs—admin, api, login—should be blocked via the package’s reserved array or application-level validation so generated paths never collide with Laravel API routes.

Which Sluggable package should you choose for Laravel—cviebrock or Spatie?

Two packages dominate Laravel projects. Both appear in my regular toolkit alongside packages covered in essential Laravel plugins. They solve the same problem with different ergonomics.

Criteriacviebrock/eloquent-sluggablespatie/laravel-sluggable
Configuration stylesluggable() method returning array per attributeHasSlug trait + getSlugOptions()
Lifecycle integrationEloquent creating/updating eventsEloquent creating/updating events
Uniqueness suffixBuilt-in uniqueSuffix with {%n}Built-in suffix increment
Custom slug logicmethod callable or custom engineusingLanguage, custom generator callbacks
Ecosystem fitStandalone; huge community examplesNatural alongside Spatie Media Library and Permission
Typical use caseBlogs, CMS pages, legal guides, directoriesApps already standardised on Spatie packages

Neither package replaces technical SEO work—they automate string generation. Pick one per project and stay consistent. Switching mid-build means migrating slug columns and redirects. If you already use Spatie Media Library everywhere, Spatie Sluggable keeps mental overhead low. For most content-heavy Laravel 13 apps, cviebrock remains the default recommendation because Stack Overflow answers, tutorials, and legacy migrations reference it directly.

Sluggable Package ChoiceNew Laravel 13 app?YescviebrockContent and SEO sitesSpatie stackSpatie SlugMedia + PermissionShared requirements either wayUnique DB index + route {model:slug}Canonical URL + redirect on change
Decision guide for Laravel URL Slugs with Sluggable Package: cviebrock for general content apps, Spatie when the stack is already Spatie-centric.

How do you wire slugs into SEO, sitemaps, and Nepali content?

Generating a slug is half the job. Production sites also need canonical tags, XML sitemap entries, breadcrumb schema, and hreflang if you publish multiple languages. On legal-tech portals I build, each guide page exposes one canonical URL derived from the named route—not a hard-coded string in Blade.

// In a controller or view composer
$canonical = route('articles.show', $article->slug);

// artesaos/seotools example
SEOMeta::setCanonical($canonical);
OpenGraph::setUrl($canonical);

Sitemap generation should iterate published models and emit loc elements from the same route helper. Mixed slug and ID URLs are a common indexation problem—pick slug-based public URLs everywhere or nowhere.

Nepali and Unicode titles

Transliterated Nepali titles slug cleanly; raw Devanagari often does not produce readable Latin paths. A pattern I've used on bilingual sites: keep title_ne for display, generate the slug from a Romanized title_en or a dedicated slug_source field. Editors can preview transliteration with a Nepali Unicode converter before publish.

public function sluggable(): array
{
    return [
        'slug' => [
            'source' => 'slug_source',
            'method' => function ($string, $separator) {
                return Str::slug($string, $separator);
            },
        ],
    ];
}

For manual slug overrides in admin panels, allow a nullable slug input but validate format server-side. Client-side JavaScript preview is fine; trust the model trait only when the field is empty on create.

Custom multi-field sources

Combine fields when titles alone are ambiguous—common in booking systems like Adventure Third Pole Trek where “Everest Base Camp” repeats across seasons:

'source' => ['title', 'season_year'],
'separator' => '-',

The package concatenates sources, slugifies the result, and still applies uniqueness rules. Keep an eye on max length—long slugs truncate awkwardly in search results.

Production Slug ArchitectureEloquent ModelSluggable traitForm RequestPolicy authorizeHTTP Layer{post:slug} routes301 redirect middlewareReserved slug guardSEO OutputCanonical + OGXML sitemapBreadcrumb schemaMySQL 9.7 / MariaDB 12.3slug VARCHAR unique indexold_slugs JSON or redirects tableRedis 8.10 cache keyed by slug optional
End-to-end Laravel URL Slugs with Sluggable Package architecture: model trait, slug-based routes, SEO metadata, and indexed storage on MySQL or MariaDB.

Queue heavy sitemap rebuilds after bulk imports. On a production Laravel application, importing five hundred legal articles without deferred slug generation can block requests; chunk inserts and let model events generate slugs per row, or temporarily disable events and run an Artisan command that walks the table. Package internals are simple Eloquent hooks—understanding that helps when you debug “slug did not generate on seed” issues, as covered in custom Laravel package development thinking: know the lifecycle before you fight it.

Frontend asset pipelines do not affect slug generation, but keep URL helpers consistent in JavaScript by passing slugs from Blade—avoid reconstructing paths in Vue or Alpine that drift from named routes. See Vite config for Laravel projects for how compiled assets still consume server-rendered route URLs.

Key Takeaways

  • Install cviebrock/eloquent-sluggable, add a unique slug column, and bind public routes with {model:slug} on Laravel 13.
  • Keep onUpdate => false for published content unless you implement 301 redirects for every slug change.
  • Use scoped uniqueness when titles repeat across categories, tenants, or regions.
  • Generate Latin slugs from Romanized or English source fields when primary titles are in Devanagari script.
  • Wire canonical URLs, sitemaps, and schema to the same named route helper—never duplicate URL strings in templates.
  • Block reserved paths (admin, api, login) in validation or package config before deploy.

People Also Ask

Does Laravel 13 include built-in slug generation?

No. Laravel ships Str::slug() for string conversion, but automatic generation on model save, uniqueness suffixes, and scoped duplicate handling come from packages like cviebrock/eloquent-sluggable or spatie/laravel-sluggable. You still own the migration, route binding, and redirect strategy.

What happens to SEO if I change a slug after publishing?

Search engines treat the new URL as a different page unless you return a 301 Moved Permanently from the old slug. Without redirects, you split ranking signals and break inbound links. Default package behaviour keeps slugs stable on update precisely to avoid accidental SEO damage.

Can I use numeric IDs in URLs and slugs only for marketing pages?

Yes—many teams expose slugs on public content while keeping IDs for admin panels and APIs. Mixed strategies work if internal links never leak ID-based URLs into the indexable site; consistency matters more than all-or-nothing adoption.

Is a unique database index on slug required?

Strongly recommended. Package-level uniqueness checks race under concurrent requests; a unique index on MySQL 9.7 or MariaDB 12.3 guarantees integrity at the storage layer and fails fast during duplicate imports.

Ship readable URLs on your next Laravel build

Laravel URL Slugs with Sluggable Package integration takes an afternoon to implement correctly and years to regret if skipped—especially on content libraries, legal guides, and product catalogues where titles change but links must endure. Start with cviebrock on Laravel 13, bind routes by slug, add a unique index, and connect canonical metadata before you launch. If you want slug architecture, SEO wiring, and deployment handled together on a business-critical site, review the portfolio or reach out via contact us for custom software development—readable URLs are baseline infrastructure, not a launch-day afterthought.

Frequently Asked Questions

No. Laravel ships Str::slug() for string conversion, but automatic generation on model save, uniqueness suffixes, and scoped duplicate handling require packages like cviebrock/eloquent-sluggable or spatie/laravel-sluggable. You still own the migration, route binding, and redirect strategy.

Search engines treat the new URL as a different page unless you return a 301 Moved Permanently from the old slug. Without redirects, you split ranking signals and break inbound links. Default package behaviour keeps slugs stable on update to avoid accidental SEO damage.

Strongly recommended. Package-level uniqueness checks can race under concurrent requests; a unique index on MySQL 9.7 or MariaDB 12.3 guarantees integrity at the storage layer and fails fast during duplicate imports.

Start with PHP 8.3 or higher and Laravel 13.x, then install cviebrock/eloquent-sluggable via Composer 2.10. Add an indexed unique slug column in your migration, apply the Sluggable trait to your model, and define sluggable() with a source field such as title. Bind public routes with scoped syntax like {article:slug} instead of numeric IDs. On content sites I maintain, I keep slug logic in the model, validation in Form Requests, and SEO metadata in artesaos/seotools alongside the sluggable trait.

Both cviebrock/eloquent-sluggable and spatie/laravel-sluggable hook into Eloquent creating and updating events and solve the same problem with different ergonomics. cviebrock uses a sluggable() method returning an array per attribute; Spatie uses HasSlug plus getSlugOptions(). cviebrock has built-in uniqueSuffix with {%n} and huge community examples—my default for blogs, legal guides, and directories on Laravel 13. Spatie fits naturally when the stack already uses Spatie Media Library and Permission. Pick one per project; switching mid-build means migrating slug columns and redirects.

Set unique => true in sluggable() and the package queries existing rows, appending -1, -2, and so on when two records share the same title. Configure uniqueSuffix, separator, maxLength, and maxLengthKeepWords for predictable output. For multi-tenant or category-scoped content, pass a scope closure so identical titles in different categories stay unique—useful on directory sites like Lawyers Pokhara where section names repeat across regions. Title changes after publish need explicit redirect planning even when duplicates are resolved automatically.

Default behaviour keeps the original slug with onUpdate => false—the right choice for published SEO URLs. Changing slugs without a 301 redirect breaks inbound links and Search Console history. If editors must rename URLs, store old slugs in a JSON column or a dedicated redirects table and fire a redirect in middleware. I treat slug stability as baseline infrastructure on legal-tech portals where guide titles get edited but inbound links must endure.

Laravel 13 supports scoped binding directly in the route definition: Route::get('/articles/{article:slug}', [ArticleController::class, 'show']). Controller type-hinting stays unchanged because Laravel resolves the model by the slug column you specified. This is cleaner than overriding getRouteKeyName() globally. Emit canonical URLs, sitemap entries, and Open Graph tags from the same named route helper—never hard-code URL strings in Blade templates.

Generating a slug is half the job. Each published page should expose one canonical URL derived from the named route, not a duplicated string in templates. With artesaos/seotools, set canonical and Open Graph URL via route('articles.show', $article->slug). Sitemap generation should iterate published models and emit loc elements from the same route helper. Mixed slug and ID URLs are a common indexation problem—pick slug-based public URLs everywhere or nowhere. Queue heavy sitemap rebuilds after bulk imports on content-heavy sites.

Transliterated Nepali titles slug cleanly; raw Devanagari often does not produce readable Latin paths. On bilingual sites I keep title_ne for display and generate the slug from a Romanized title_en or a dedicated slug_source field. Editors can preview transliteration with a Nepali Unicode converter before publish. For manual overrides, allow a nullable slug input in admin panels but validate format server-side with a regex rule. Client-side JavaScript preview is fine; trust the model trait only when the field is empty on create.

Reserved slugs are path segments such as admin, api, and login that would collide with Laravel application routes if assigned to content records. Block them via the package reserved array or application-level validation before deploy so generated paths never hijack system endpoints. Validate custom slug input in Form Requests with a regex like lowercase alphanumeric segments separated by hyphens. I treat reserved-path blocking as part of launch checklist alongside unique indexes—not an optional polish step.

Yes—many teams expose slugs on public content while keeping IDs for admin panels and APIs. Mixed strategies work if internal links never leak ID-based URLs into the indexable site; consistency matters more than all-or-nothing adoption. Search engines and analytics become readable when public-facing guides, products, and directory listings share one slug-based pattern. The sluggable package still generates and stores the slug column regardless; routing and SEO layers decide where it surfaces publicly.

Global uniqueness blocks identical titles across your entire table, which breaks down when the same headline appears in different categories, tenants, or regions. Pass a scope closure in sluggable() config: the package checks uniqueness only within that query scope, such as where category_id matches the current model. Identical titles in different categories then produce identical base slugs without collision. This pattern appears on directory sites and multi-region legal guides where section names repeat but public URLs must stay distinct within each scope.

A slug is the human-readable URL segment—/court-marriage-requirements-nepal instead of /posts/847. Search engines treat descriptive paths as a weak relevance signal, but the bigger win is operational: editors understand URLs, analytics reports become readable, and Laravel SEO setup stays consistent across sitemaps, breadcrumbs, and Open Graph tags. Manual slug fields in admin panels break down when someone duplicates a title, pastes uppercase text, or leaves the slug empty. Package automation applies consistent kebab-case rules and numeric suffixes on collision.

Importing hundreds of articles without planning can block requests because slug generation runs on Eloquent model events. Chunk inserts and let model events generate slugs per row, or temporarily disable events and run an Artisan command that walks the table afterward. Package internals are simple Eloquent hooks—understanding the lifecycle helps debug slug did not generate on seed issues. A unique database index catches race-condition duplicates during concurrent or bulk writes that package-level checks alone might miss.

Share this article

0 Comments

Leave a comment

Your email is not published. Comments appear once they have been read. Sign in to have your details filled in.

Quick Contact Options
Choose how you want to connect me: