Installation
Requirements
- PHP 8.4+
- Laravel 13+ — 13.17+ only for the optional
laravel/headintegration, which is that package's own floor - PostgreSQL 18+, MySQL 8.4+, or SQLite
- The
bcmathorgmpextension, only if you useSqidsEncoder
The uniqueness guarantees are enforced natively on each engine — a functional partial unique index on PostgreSQL and SQLite, equivalent generated key columns on MySQL — and the full suite runs against all three. So Polyslug works on Laravel Cloud (serverless Postgres plus MySQL 8.4 LTS) with no extra configuration.
Install
composer require pushery/polyslug-for-laravel
The service provider registers itself through package discovery.
Migrate
Polyslug stores its slugs in a polyslug_slugs table whose migration is registered
automatically, so a plain migrate is enough:
php artisan migrate
The package ships its migrations in order, and migrate applies all of them:
| Migration | What it does |
|---|---|
create_polyslug_slugs_table | The three tables — polyslug_slugs (every slug a model has ever had), polyslug_tokens (the key-to-token store the RandomTokenEncoder uses) and polyslug_short_links (the /go tokens) — plus the unique indexes that carry the uniqueness guarantees. |
add_enforce_unique_to_polyslug_slugs | The enforce_unique column behind shared slugs. |
scope_polyslug_tokens_to_a_model_type | Gives each morph type its own token space, so Page#1 and Wishlist#1 no longer share a token. |
add_polyslug_slugs_resolution_index | The index every incoming URL uses. Without it, resolution was a full table scan. |
pin_polyslug_token_collation | MySQL only: pins the token columns to a byte-exact collation, so a mixed-case alphabet keeps its entropy. A no-op on PostgreSQL and SQLite, which already compare byte-exactly. |
reorder_polyslug_slugs_resolution_index | Rebuilds the resolution index with the slug ahead of the scope, so a slug-only URL seeks on the slug whether or not the model names a scope. The new index is built before the old one is dropped. |
add_retired_at_to_polyslug_slugs | The retired_at column behind retired slugs. Null on every existing row, so nothing changes until a slug is retired. |
pin_polyslug_key_collation | MySQL only: pins the columns that say which record a row belongs to and under which name, in all three tables, to a byte-exact collation. Every lookup then tells two names or two record keys apart the way the unique keys do: café from cafe, the scope Team from team, a record keyed Abc from one keyed abc. A no-op on PostgreSQL and SQLite, which already compare byte-exactly. |
pin_polyslug_slug_collation | PostgreSQL only, and only on a database whose locale folds an ASCII capital differently from PHP, as a Turkish or Azerbaijani one folds I to ı: pins the slug column to the C collation, so a slug that keeps its capitals with preserveCase collides and resolves the same way on every database. Rebuilds the indexes on the column once. A no-op everywhere else. |
store_polyslug_times_as_datetime_on_mysql | MySQL only: turns the time columns of the three tables from TIMESTAMP into DATETIME, which holds a date after 2038-01-19 and is not converted through the session's time zone. Every value reads as it did before. Rewrites each table once. A no-op on PostgreSQL and SQLite. |
The two optional tables stay empty and inert until you use the feature behind them. See the database reference.
To manage the migrations in your application instead of loading them from the package,
publish them and disable the automatic registration in a service provider's register():
use Polyslug\PolyslugServiceProvider;
PolyslugServiceProvider::ignoreMigrations();
Publishing the package files
Nothing has to be published to use Polyslug. Publish what you want to customize:
php artisan vendor:publish --tag=polyslug-config # config/polyslug.php
php artisan vendor:publish --tag=polyslug-migrations # the slug + token tables
php artisan vendor:publish --tag=polyslug # both at once
Polyslug ships no views and no translations — it routes and resolves, it renders nothing and emits no user-facing text — so there is nothing else to publish.
Every option in the published config/polyslug.php is documented inline; the same options
are listed in the configuration reference.
Verify the setup
php artisan polyslug:doctor
The command checks that the configured encoder is valid and that the uniqueness-guaranteeing indexes exist, and exits non-zero when they do not. See Diagnostics.
Next
- Quick start — a sluggable model and a self-healing route.
- Backfilling existing rows — if your tables predate Polyslug.