pushery/polyslug-for-laravel
| Install | |
|---|---|
composer require pushery/polyslug-for-laravel |
|
| Latest Version: | v0.25.0 |
| PHP: | ^8.4 |
| Laravel: | 13 |
| License: | MIT |
| Last Updated: | Oct 21, 2026 |
| Links: | GitHub · Packagist |
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.
- The
bcmathorgmpextension, only if you useSqidsEncoder
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.
Related Packages
The registry of a site's public addresses: one flat namespace, one row per addre...
Easy creation of slugs for your Eloquent models in Laravel
Provides a HasSlug trait that will generate a unique slug when saving your Larav...
Automatic slug generation for Eloquent models via the #[Sluggable] attribute.
Version History
| Version | Released | PHP | Laravel | License |
|---|---|---|---|---|
| v0.25.0 | ^8.4 | ^13.0 | MIT | |
| v0.24.0 | ^8.4 | ^13.0 | MIT | |
| v0.23.0 | ^8.4 | ^13.0 | MIT | |
| v0.22.1 | ^8.4 | ^13.0 | MIT | |
| v0.22.0 | ^8.4 | ^13.0 | MIT | |
| v0.21.0 | ^8.4 | ^13.0 | MIT | |
| v0.20.1 | ^8.4 | ^13.0 | MIT | |
| v0.20.0 | ^8.4 | ^13.0 | MIT | |
| v0.19.0 | ^8.4 | ^13.0 | MIT | |
| v0.18.9 | ^8.4 | ^13.0 | MIT | |
| v0.18.8 | ^8.4 | ^13.0 | MIT | |
| v0.18.7 | ^8.4 | ^13.0 | MIT | |
| v0.18.6 | ^8.4 | ^13.0 | MIT | |
| v0.18.5 | ^8.4 | ^13.0 | MIT | |
| v0.18.4 | ^8.4 | ^13.0 | MIT | |
| v0.18.3 | ^8.4 | ^13.0 | MIT | |
| v0.18.2 | ^8.4 | ^13.0 | MIT | |
| v0.18.1 | ^8.4 | ^13.0 | MIT | |
| v0.18.0 | ^8.4 | ^13.0 | MIT | |
| v0.17.1 | ^8.4 | ^13.0 | MIT | |
| v0.17.0 | ^8.4 | ^13.0 | MIT | |
| v0.16.0 | ^8.4 | ^13.0 | MIT | |
| v0.15.0 | ^8.4 | ^13.0 | MIT | |
| v0.14.0 | ^8.4 | ^13.0 | MIT | |
| v0.13.0 | ^8.4 | ^13.0 | MIT | |
| v0.12.0 | ^8.4 | ^13.0 | MIT | |
| v0.11.0 | ^8.4 | ^13.0 | MIT | |
| v0.10.0 | ^8.4 | ^13.0 | MIT | |
| v0.9.0 | ^8.4 | ^13.0 | MIT | |
| v0.8.2 | ^8.4 | ^13.0 | MIT |
Showing the latest 30 of 45. See every release on Packagist