Str::slug

Transliterates, lowercases and hyphenates a string into a URL-safe slug.

Signature
Str::slug(string $title, string $separator = '-', ?string $language = 'en', array $dictionary = ['@' => 'at']): string
  • Added in Laravel 4.0

Parameters

ParameterTypeDescription
$$titlestringThe text to convert into a slug.
$$separatoroptionalstring= '-'The character placed between words. Whichever of '-' or '_' is not chosen is itself converted into the separator.
$$languageoptionalstring|null= 'en'Language passed to the ASCII transliterator, which selects locale-specific rules. Pass null to skip transliteration entirely and keep non-ASCII letters.
$$dictionaryoptionalarray<string, string>= ['@' => 'at']Literal substrings expanded into words before stripping punctuation. Each replacement is wrapped in the separator.

Return value

stringA lowercase string containing only letters, numbers and the separator, trimmed of leading and trailing separators.

Changelog

  • PHP 5.5The $language argument was added, enabling locale-aware transliteration such as German ä to ae.
  • PHP 9.xThe $dictionary argument was added, defaulting to ['@' => 'at'], so slugs that previously dropped '@' now contain 'at'.

Str::slug is the URL-safe workhorse. It transliterates the string to ASCII, expands the dictionary entries, lowercases everything, deletes every character that is not a letter, a digit or whitespace, then collapses whitespace and separator runs into a single separator and trims the ends.

Use it whenever a human-written title has to become a path segment, a cache key, an anchor id or a filename. The output is stable and safe to put in a URL without further encoding.

What trips people up

  • Slugs collide. Punctuation is deleted, not encoded, so C# and C++ both reduce to c. If the slug is a unique key, check the database and append a counter.
  • Transliteration is best-effort. Scripts without a mapping — CJK, most emoji — vanish entirely, which can leave you with an empty string. Guard against that before saving.
  • The $language argument changes the letters you get: Bär is bar under en but baer under de.
  • Passing null for the language skips transliteration, so accented letters survive because \pL matches them. That produces a valid but non-ASCII slug.
  • Changing an existing record's slug breaks inbound links. Generate once and store it.

Example

Try it yourself
Loading editor…
Press Run to execute the code.

When to use it

  • Build the /blog/{slug} path segment for a post title entered by an author.
  • Turn an uploaded file's original name into a safe storage filename.
  • Create a deterministic cache key or HTML anchor id from a heading.

Frequently asked questions

Why does Str::slug return an empty string?

Because every character in the input was either punctuation, which is deleted, or a script the transliterator has no ASCII mapping for, such as Chinese or emoji. Fall back to a record id or a literal default whenever the result is empty.

How do I keep accented characters in a slug?

Pass null as the third argument: Str::slug('Ünïcödé', '-', null) returns 'ünïcödé'. The character filter keeps any Unicode letter, so only the transliteration step is skipped.

Does Str::slug guarantee a unique URL?

No. Different titles routinely produce the same slug because punctuation is stripped rather than encoded, so you must check for an existing row and append a suffix before saving.

More examples

Customising the dictionary and the language

Prints "the-10-best-laravel-tips", then "r-and-d-budget", then "ünïcödé". The second line shows that a custom dictionary rescues meaning that would otherwise be deleted — without it the slug would be "r-d-budget". The third shows that a null language leaves non-ASCII letters in place, because the character filter keeps any Unicode letter.

Example · php
<?php

use Illuminate\Support\Str;

// Messy input: mixed case, em dash, double spaces, trailing punctuation.
echo Str::slug('  The 10 BEST — Laravel  Tips! '), PHP_EOL;

// Teach it that "&" means "and" instead of dropping it.
echo Str::slug('R&D Budget', '-', 'en', ['&' => 'and']), PHP_EOL;

// Skip transliteration entirely and keep the original letters.
echo Str::slug('Ünïcödé', '-', null), PHP_EOL;

Guaranteeing a unique slug on save

The pattern every content site needs: slug once on create, fall back to a literal when the title produces an empty slug, and append a counter until the value is free. Because the slug is written on creating and never recomputed, editing a title later cannot break existing URLs. This example needs a booted application and a database, so it is displayed rather than executed.

Example · php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Str;

class Post extends Model
{
    protected static function booted(): void
    {
        static::creating(function (Post $post) {
            $base = Str::slug($post->title) ?: 'post';
            $slug = $base;
            $i = 2;

            while (static::where('slug', $slug)->exists()) {
                $slug = $base.'-'.$i++;
            }

            $post->slug = $slug;
        });
    }
}

Discussion

  • Be the first to comment on this lesson.