Skip to main content

Configuration

Publish the configuration file to customize it:

php artisan vendor:publish --tag=polyslug-config

Every option in the published config/polyslug.php is documented inline. Nothing has to be published to use Polyslug — the package config is merged, so an unpublished installation runs on the defaults below.

encoder

'encoder' => \Polyslug\Encoders\RandomTokenEncoder::class,

The class that maps a model's key to and from the opaque token in its URL. Must implement Polyslug\Contracts\IdentityEncoder. A model can override it with the encoder attribute option.

The default stores an unguessable random token per key, so the URL reveals nothing. SqidsEncoder was the default until 0.5.0 and remains supported — but its token decodes back to the primary key, which leaks the key, the creation order and the growth rate. See Identity encoders.

random_token

'random_token' => [
'length' => 16, // characters in a newly issued token
'alphabet' => null, // null uses 0-9a-z
],

Used by RandomTokenEncoder. length is a floor, not a fixed width: a length whose space fills up yields to one character more rather than failing to issue a URL. Changing it is safe at any time — tokens are stored, never recomputed from the key, so existing URLs keep resolving and only new records use the new length.

A custom alphabet must be made of URL-unreserved characters (A-Z a-z 0-9 - . _ ~) and must not repeat one. Per-model overrides go through the encoderOptions attribute option, which takes the same two keys. See Token length.

sequential_token

'sequential_token' => [
'length' => 1, // width the counting starts at
'alphabet' => null, // null uses 0-9a-z
],

Used by SequentialTokenEncoder, which hands out the shortest token not yet taken. length is where the counting starts — it moves the first token, it does not scatter the ones after it, and it must not be read as making a counted token unguessable. See SequentialTokenEncoder.

'short_links' => [
'scheme' => 'random', // 'random' or 'sequential'
'length' => null, // null takes the scheme's own default: 10 random, 1 counted
'alphabet' => null,
],

The token space behind $model->shortLink() and the /go/{token} route — separate from the identity token above, because a link that is printed, spoken or put on a QR code wants a different trade from the one inside every URL.

length is null rather than a number on purpose: ten random characters is a short link, ten counted ones is 0000000000 for the first record, so the sensible default belongs to the scheme rather than to this section. Set it explicitly to override either. Bind Polyslug\Contracts\TokenScheme yourself to replace the scheme entirely. See Short links.

sqids

'sqids' => [
'alphabet' => null, // null uses the Sqids default alphabet
'min_length' => 0, // pad tokens to at least this many characters
],

Used only by SqidsEncoder. A custom alphabet shuffles the token space for this application — set one and keep it stable, because changing it changes every previously generated URL. Per-model overrides go through the encoderOptions attribute option.

legacy_decoders

'legacy_decoders' => [],

IdentityEncoder classes to try, in order, when the current encoder cannot decode a token. This is what lets you switch encoders without breaking published URLs: old links still resolve, and the canonical middleware 301s them to the new-format URL. See migrating encoders.

write

'write' => [
'max_attempts' => 5,
],

How many times a slug write regenerates and retries after losing a race to a concurrent writer, before throwing Polyslug\Exceptions\CouldNotWriteSlug. On failure the model keeps its previous slug — restored in place, not rolled back. See How it works.

locale

'locale' => [
'source' => 'app', // 'app' | 'route'
'route_param' => 'locale', // the route parameter holding the locale
'missing' => 'fallback', // 'fallback' | 'id-only'
'fallback_locale' => null, // null uses the application's fallback locale
],
  • source — how the canonical-redirect middleware decides which locale a request is for. 'app' uses the active application locale; 'route' reads the route_param segment, so a /{locale}/… URL is compared against the right locale's slug even when the app locale differs.
  • route_param — the name of that route parameter.
  • missing — the route key when a locale has no slug yet: 'fallback' uses the fallback locale's slug, 'id-only' emits a slug-less _TOKEN key.
  • fallback_locale — overrides the application's fallback locale. Note that this reaches further than missing: it is also the locale x-default points at in the hreflang set.

See Locale-aware routing.

reserved

'reserved' => [
'global' => [],
'from_routes' => false,
],
  • global — app-wide slugs that may never be assigned to any model, merged with each model's own reserved option. A reserved base is suffixed (adminadmin-2) like any other collision.
  • from_routes — when true, additionally reserves the static first segment of every registered route, so a generated slug can never shadow a real route.

See Reserved slugs.

gone

'gone' => [
'status' => 410,
'redirect_status' => 301,
],

The status returned for a model whose polyslugIsGone() is true, and the redirect status used when polyslugSupersededBy() returns a successor. See Gone and superseded content.

analytics

'analytics' => [
'enabled' => false,
],

When enabled, the canonical middleware dispatches a Polyslug\Events\SlugRedirected event on every self-healing redirect. Off by default; nothing writes synchronously on the hot path unless a listener does. See Events.

sitemap

'sitemap' => [
'types' => [
// \App\Models\Page::class,
],

'max_urls' => 50_000,
'max_bytes' => 50 * 1024 * 1024,
],

types is the sluggable models polyslug:sitemap includes. Each must be an Eloquent model implementing Sluggable. A Polyslug\Contracts\PolyslugUrlResolver must be bound in the container so the command can build absolute URLs.

max_urls and max_bytes are the sitemap protocol's own ceilings for one file: 50,000 URLs and 50 MB uncompressed. Past either, the command writes numbered parts beside --path and a <sitemapindex> at --path itself, so the limit is never something you have to notice. Lower them if a CDN or a search console you use wants smaller files; raising them past the protocol produces a document engines reject. See Sitemaps.

backfill

'backfill' => [
'connection' => null,
'queue' => null,
'tries' => null,
'timeout' => null,
],

Where polyslug:backfill --queue puts its jobs. A backfill walks an entire table, so on the default queue it sits in front of every password reset and order confirmation the application has, for as long as that takes. Name a queue your workers treat as bulk and the rest of the app keeps moving.

null leaves the framework's own default in place for each. connection and queue can be overridden per run with --on-connection= and --on-queue=. tries and timeout are the job's, not the worker's: a chunk re-queries its own rows, so a retry is safe, and a large chunk needs a timeout that admits it. See Backfilling existing rows.

open_graph

'open_graph' => [
'locale_map' => [
// 'en' => 'en_US',
// 'de' => 'de_DE',
],
],

Open Graph writes a locale as language_TERRITORY (en_US, pt_BR). A plain en is outside that format, and a scraper that cannot parse the value does not read a language from it — it falls back to its own default. So a locale with no territory gets no og:locale tag rather than an unparseable one.

The territory is not something the package can invent: en is en_US to one site and en_GB to another, and asserting either would announce a regional variant nobody configured. Name the pairs you want here. Locales that already carry a territory (pt_BR, de-AT) need no entry. See laravel/head integration.

redirect

'redirect' => [
'status' => 301,
],

The status for the self-healing redirect from a stale slug to the canonical URL, on GET/HEAD only. 301 is permanent; use 302 or 307 while a slug is still volatile so the redirect is not cached. 308 is accepted as well, and since only GET and HEAD are ever redirected, its method-preserving guarantee changes nothing here. See Self-healing routes.

resolution

'resolution' => [
'require_scope' => false,
],

A slug-only URL carries no id, so the slug alone has to identify the record. On a model scoped with #[Polyslug(scope: ...)] that only holds within a scope: the unique index is scope-bound, so /@alice/toolkit and /@bob/toolkit may both hold toolkit legitimately.

The resolution gate does not separate them — it answers what the environment says is visible, while a scope in a path segment is an argument of the resolution. Hand it over with polyslugResolutionScope().

Turn this on and a scoped model whose caller names no scope is refused instead of resolving to whichever row sorts first. Off by default: switching it on refuses every scoped model that does not answer yet. See Slug-only URLs.

types

'types' => [
// 'page' => \App\Models\Page::class,
],

Maps a URL {type} segment to a sluggable model for the polymorphic resolver, so a single /{type}/{slug} route serves every content type. Each value must be an Eloquent model implementing Sluggable. See Polymorphic routing.