Str::slug
Transliterates, lowercases and hyphenates a string into a URL-safe slug.
Str::slug(string $title, string $separator = '-', ?string $language = 'en', array $dictionary = ['@' => 'at']): string- Added in Laravel 4.0
Parameters
| Parameter | Type | Description |
|---|---|---|
$$title | string | The text to convert into a slug. |
$$separatoroptional | string= '-' | The character placed between words. Whichever of '-' or '_' is not chosen is itself converted into the separator. |
$$languageoptional | string|null= 'en' | Language passed to the ASCII transliterator, which selects locale-specific rules. Pass null to skip transliteration entirely and keep non-ASCII letters. |
$$dictionaryoptional | array<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#andC++both reduce toc. 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
$languageargument changes the letters you get:Bärisbarunderenbutbaerunderde. - Passing
nullfor the language skips transliteration, so accented letters survive because\pLmatches them. That produces a valid but non-ASCII slug. - Changing an existing record's slug breaks inbound links. Generate once and store it.
Example
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.
<?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.
<?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