pushery / polyslug-for-laravel
Polymorphic, multilingual routable identity for Eloquent — leak-safe IDs, per-locale slugs, and hreflang out of the box.
Requires
- php: ^8.4
- ext-json: *
- illuminate/collections: ^13.0
- illuminate/console: ^13.0
- illuminate/container: ^13.0
- illuminate/contracts: ^13.0
- illuminate/database: ^13.0
- illuminate/filesystem: ^13.0
- illuminate/http: ^13.0
- illuminate/routing: ^13.0
- illuminate/support: ^13.0
- illuminate/view: ^13.0
- sqids/sqids: ^0.5.0
Requires (Dev)
- larastan/larastan: 3.10.0
- laravel/head: ^0.2.0
- laravel/pao: ^1.1
- laravel/pint: 1.30.5
- orchestra/testbench: ^11.0
- pestphp/pest: ^5.0
- pestphp/pest-plugin-agent: ^5.0
- pestphp/pest-plugin-browser: ^5.0
- pestphp/pest-plugin-evals: ^5.0
- pestphp/pest-plugin-laravel: ^5.0
- pestphp/pest-plugin-mutate: ^5.0.1
- pestphp/pest-plugin-phpstan: 5.2.0
- pestphp/pest-plugin-rector: ^5.0
- pestphp/pest-plugin-type-coverage: 5.0.2
- phpstan/phpstan: 2.2.9
- rector/rector: 2.6.4
- spaze/phpstan-disallowed-calls: 4.14.0
- tomasvotruba/type-coverage: 2.3.4
Suggests
- laravel/head: Lets a Polyslug model describe its own <head>: Head::polyslug($model) writes the canonical URL, the reciprocal hreflang set, the Open Graph locale set and the robots directive from the model's own slug data. Needs Laravel 13.17+, which is laravel/head's own floor - Polyslug itself needs only 13.0.
Provides
None
Conflicts
None
Replaces
None
README
Polyslug
Polymorphic, multilingual routable identity for Eloquent. Pretty URLs that are safe to expose, safe to rename, and correct across languages — with leak-free IDs, self-healing canonical redirects, and hreflang built in.
/blog/laravel-routing-explained_aB3xK
└──────── slug ─────────┘ └id─┘
changeable, localized, SEO-friendly stable, opaque, resolves the model
A slug should be free to change. A URL should never break. Those two goals usually fight each other. Polyslug settles the fight by splitting a URL into two independent parts — a human-readable slug (pretty, per-locale, editable) and a stable opaque identity (an encoded token that resolves the model). Rename the slug all you like: the identity still resolves, and old URLs redirect themselves to the new canonical one.
Install
composer require pushery/polyslug-for-laravel php artisan migrate
Mark a model as sluggable, point a route at it, and you are done:
#[Polyslug(source: 'title')] class Page extends Model implements Sluggable { use HasPolyslug; } Route::polyslug('/pages/{page}', [PageController::class, 'show'])->name('pages.show');
Rename the page and the old URL 301s to the new one, by itself.
Why Polyslug?
- 🔒 Leak-safe IDs by default. URLs carry an encoded token, not
/pages/1523. No exposed row counts, no enumerable primary keys. The default encoder issues a random, unguessable token per row — the only shipped encoder that makes that claim true end to end. The choice is yours, though: Sqids, UUID, ULID, the raw key, or your own. - 📏 URLs as short as you want them. The URL is
{slug}_{token}by default, and both halves are yours to drop or shrink:slugless: truemakes it the token alone (/lists/k3f9dlq7),idLess: truemakes it the slug alone, and the token's length and alphabet are settings — per application or per model. A length that runs out of tokens widens by a character instead of failing, so a short one is a real choice. Need the shortest URL there is? A counted scheme hands out0,1, …z, then00. - ♻️ Self-healing URLs. Rename freely. A stale slug on a
GET/HEADrequest is301-redirected to the current canonical URL automatically — no redirect tables to hand-maintain, no dead links, no lost link equity. - 🌍 Multilingual with hreflang out of the box. One slug per locale, and a
reciprocal
hreflangset (plusx-default) generated from the same resolver that builds your canonical URL — so they can never drift apart. - 🧩 Polymorphic routing. Serve every content type — pages, articles, products —
through a single
{type}/{polyslug}route and one registry. - 🧭 Stable resolution. Route-model binding decodes the identity, not the slug, so
a mistyped or outdated slug still finds the right model (then redirects). An unknown
or malformed token is a clean
404— never a fuzzy match. - 🏢 Scoped uniqueness. Uniqueness can be scoped per tenant, per locale, per category — whatever columns you name.
- 🗂️ History, events & immutability. Superseded slugs are kept so old URLs keep
resolving; a
SlugChangedevent fires on every change; slugs can be frozen. - 🤝 Writes its own
<head>vialaravel/head. Install Laravel's<head>package andHead::polyslug($model)fills in the canonical URL, thehreflangset, the Open Graph locales and arobotsdirective for models your visibility gate hides. Optional — Polyslug requires nothing beyond slimilluminate/*components either way. - ✅ Proven on the databases you deploy to. The one-current-slug guarantee ships as a functional partial unique index on PostgreSQL and SQLite and as generated key columns on MySQL — two genuinely different mechanisms, and the suite proves both against real PostgreSQL 18 and MySQL 8.4 servers rather than inferring one from the other.
Documentation
Full docs at docs.pushery.com/polyslug-for-laravel.
- Installation — requirements, the migration, publishing
- Quick start — a sluggable model and a self-healing route
- Features — encoders, hreflang, nested paths, sitemaps, short links
- laravel/head — canonical URL, hreflang and indexability in Laravel's
<head>package - Recipes — twelve app shapes wired end to end
- Reference — every config key, option, command, event and contract
Requirements
- PHP 8.4+
- Laravel 13+ — 13.17+ only for the optional
laravel/headintegration, which is that package's own floor, not Polyslug's - PostgreSQL 18+, MySQL 8.4+, or SQLite — the uniqueness guarantees are enforced natively on each, and the full suite runs against all three. So Polyslug works on Laravel Cloud (serverless Postgres + MySQL 8.4 LTS) with no extra configuration.
Security
Please review the security policy and report vulnerabilities privately rather than opening a public issue.
Built by Pushery
This package is built and maintained by Pushery — a Berlin-based studio building Laravel applications, SaaS products, and open-source tools.
Building a Laravel UI? WireKit, Pushery's open-source Livewire component kit, gives you a polished component library out of the box. Browse the rest of our work at pushery.com.
License
The MIT License (MIT). See LICENSE for details.