Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Polyslug For Laravel Laravel Package

pushery/polyslug-for-laravel

Polyslug for Laravel adds polymorphic slug support to Eloquent models, letting multiple models share a single slug table with automatic generation, uniqueness checks, and route binding. Keep SEO-friendly URLs without duplicating slug logic across models.

View on GitHub
Deep Wiki
Context7

Technical Evaluation

Architecture fit. Polyslug extends Laravel through the framework's own seams rather than around them: a #[Polyslug] attribute for per-model configuration, a HasPolyslug trait for behavior, a Sluggable contract for type-hinting, a Route::polyslug() macro that composes SubstituteBindings with the canonical middleware, and an IdentityEncoder interface as the swap point for ID obfuscation. That is the shape a Laravel reviewer expects, so design review friction is low and adoption can be per-model (via the polymorphic types registry) rather than a flag-day migration.

The consequential design decision is the inversion of the route key: {slug}_{encodedId} resolves on the token, not the slug. The slug is decoration that is free to change; every slug a record has ever held stays in polyslug_slugs with an is_current flag, so stale URLs still bind and then 301 to canonical. For a TPM this converts a recurring, expensive defect class — "renaming a published page broke inbound links and search ranking" — from a redirect-spreadsheet maintenance chore into a non-event. v0.24.0 extends that same guarantee one level deeper, from slug identity to record identity: #[Polyslug(identityOf: 'public_id')] names the column the token was minted from, and a URL carrying a full old id still resolves after the identity column has moved on (a record merge or re-keying), with the canonical middleware answering 301 to the token of today; identitySecond names a column of a second id so a URL carrying {whole id}-{second id} resolves to the record both name. Lookup enumerates candidates via TailTokens::apart() and stays on the identity column's index. That pulls "we consolidated two records and the old link 404'd" into the same non-event category as renames — genuinely useful for content consolidation and dedup workflows — but it also forces an explicit decision about whether our identity column is stable by design, which is a new item on an already-wide decision surface. The same release improves the collision model behind the deferred slugless/idLess posture: TailTokens::apart() now takes a second id per entry, so two records sharing an id answer under {id}-{second id} instead of {id}-2 (with -2, -3 retained only when second ids also collide), and idsIn()/tokensFor() expose the token/id mapping for introspection. POLYSLUG_BACKFILL_TRIES adds one env override for queued backfill attempts. The package also ships the multilingual SEO plumbing (hreflangLinks(),, hreflangTags(), sitemapAlternateTags(), locale-aware canonical redirects) that teams otherwise hand-roll badly, and polyslugResolveQuery() doubles as a draft/tenant isolation lever, not just a security control.

Integration feasibility. The mechanical cost is genuinely small: php artisan make:polyslug Page scaffolds the model, the polyslug_slugs migration auto-registers, and a route becomes Route::polyslug('/pages/{page}', ...). A spike is roughly half a day, plus a merge/identity-churn scenario if our data model consolidates records. The real cost is the decision surface, which is wide and semantically dense: ~20 attribute options (now including identityOf/identitySecond), ~30 config keys, env overrides, four-plus encoders, locale sourcing, route-derived reserved words, soft-delete retention, nested scoping, and bulk resolution. Nothing is gratuitous — each option maps to a real failure mode — but a plausible-looking config can be wrong in ways that only surface under tenancy, localization, identity changes, or rename traffic.

Technical risk. The dominant risk is longevity, not code quality. A single vendor namespace (pushery), no visible support or upgrade policy, and a small community footprint remain; the new v0.24.0 is a positive cadence signal but the identity-resolution path in particular is a fresh code path that should not be treated as battle-tested. MIT gives a fork escape hatch, but a fork means we own it. The behavioral risks worth sizing:

Risk Blast radius Note
Encoder change without legacy_decoders Highest Every previously published URL 404s simultaneously — the single most dangerous misconfiguration.
identityOf unset while an identity column is reassigned High Merge/re-key silently 404s old full-id URLs; the feature only helps if configured before the identity moves.
Open default polyslugResolveQuery() High Any slug resolves to any row; the canonical Location header can leak unpublished or foreign-tenant existence.
Canonical middleware runs the action before redirecting Medium Side effects in GET handlers (counters, impression logging, cache warming) fire on redirected requests; latency doubles.
301s cached permanently at browser/CDN High A wrong polyslug.locale.source yields wrong-language canonicals that are painful to unwind at the edge.
reclaimActive displacing a current slug Medium Displaced record has no current slug until resynced — needs a SlugReclaimed listener to avoid a transient 404 window.
TailTokens::apart() candidate enumeration Medium Confirm the candidate set for a stale full id is bounded (by second id / -N), index-backed, and safe on replicas; pathological ties could widen lookup.
New identity-resolution path (< one minor old) Medium Verify on our DB engine in the spike; do not assume 0.24.0 behavior is proven at scale.
Missing ResolvesSluggableChildren on a parent Low–medium Nested scopeBindings() routes 404 silently; only tests catch it.
Encoder security semantics Medium Sqids is reversible and leaks PK, creation order, and growth rate; UlidEncoder leaks creation time.

Key questions.

  1. What is the supported Laravel/PHP matrix, and is there CI evidence it tracks new Laravel majors on release day?
  2. Is there a documented upgrade path between minors (0.23 → 0.24 → next), or is the API unstable pre-1.0?
  3. Can canonical middleware 301 bare legacy slugs (no token) from a pre-adoption URL scheme, or do we need a shim?
  4. Is the middleware Octane-safe (per-request state, long-lived workers) and does the token store tolerate read replicas?
  5. What is the growth profile of polyslug_slugs / polyslug_tokens at our record × locale count, and is there archival guidance?
  6. Is polyslugResolveQuery() safe-by-default for tenants, or must every adopting team remember to override it?
  7. Does our identity column ever change (merges, legacy-ID migration), and if so is identityOf/identitySecond configured before that happens?
  8. How many candidates can TailTokens::apart() enumerate for a stale full id, is that bounded, and does it stay on the identity index on our DB engine?
  9. Can a stale full id written under an old encoder still resolve, i.e. how do identityOf and legacy_decoders interact?
  10. Who maintains this, and what is the continuity plan if they stop?

Integration Approach

Stack fit. Adopt where public URL identity matters and changes over time: CMS/content pages, docs and blogs with translated editions, product or listing catalogs, marketplace listings, multi-tenant SaaS with per-tenant public pages, and anything with soft deletes or record consolidation where linked-but-merged-or-deleted records must keep resolving. Skip where: single locale and slugs realistically never change (spatie/laravel-sluggable or a column plus Str::slug() is the honest answer); routing is owned by a CMS or commerce platform; the stack is polyglot with only one PHP service; or procurement requires an SLA, named counterparty, or support contract — a low-star, zero-dependent package cannot supply that regardless of quality.

Migration path. Treat it as a wrapped dependency, not a direct one:

  • Pin an exact version and wrap currentSlug(), polyslugUrls(), hreflangTags(), and sitemap emission behind an internal presenter so the surface we touch is narrow.
  • Write contract tests around resolution and redirect behavior (stale slug → 301, merged/old identity → 301, wrong-locale → canonical, tenant/draft scope → 404 indistinguishable from nonexistent).
  • Backfill existing URLs into polyslug_slugs and prior identities that record merges have since retired, so historical links heal rather than 404; decide explicitly whether a legacy bare-slug 301 shim is needed for pre-adoption URLs, and set POLYSLUG_BACKFILL_TRIES for large queued backfills.
  • Record the encoder decision and the identity-column contract (which column is identity, is it ever reassigned, do we merge records) in writing before the URL set is public and indexed. legacy_decoders buys a migration path, not a free one.

Compatibility. Verify Laravel version support and CI currency; confirm the two-id lookup and TailTokens enumeration are index-backed and bounded on our DB engine (the case-insensitive unique index is a cross-engine concern on PostgreSQL/SQLite); confirm Octane and read-replica behavior; and align on cache semantics — a configurable redirect status is the mitigation for edge-cached 301s.

Sequencing. Gate the roadmap on decisions, not sprints:

  1. Two-model spike against our messiest models (one localized, one tenant-scoped or nested), plus a merge/identity-churn scenario if we consolidate records.
  2. Written encoder decision + identity-column contract + config decisions (locale source, onDelete retention, reserved-word strategy, scope contract).
  3. Ship a read-only URL surface behind a flag: canonical URLs serve, renames and merged identities redirect, telemetry on redirect volume and inbound 404s.
  4. Expand locale coverage and hreflang/sitemap emission once redirect behavior is proven.
  5. Only then consider slugless short links, idLess pretty URLs, or immutable postures — these are product-posture switches about brand, shareability, and permanence; TailTokens::apart()'s second-id disambiguation makes idLess marginally more viable, but it is still a posture call and is declarative, so it is cheap to revisit.

Order matters: i18n decisions, encoder choice, and the identity-column contract must precede content scale, because the URL set becomes an indexed contract the moment it is public.

Operational Impact

Maintenance. A single-maintainer dependency with limited dependents is a standing risk we carry, not a one-time review item. Mitigations are cheap and concrete: pin the version, keep the internal surface narrow, run an upgrade ritual that includes reviewing the auto-registered migration (schema changes ship with upgrades), and verify release cadence each quarter — v0.24.0 is evidence of active cadence, but the new identity path is young and should be re-checked after the next minor. If the package stalls, MIT plus the wrapper means fork or replace without touching product code.

Support. There is no SLA and no named support counterparty; the documentation is unusually rigorous — it names its own failure modes (side effects firing before redirect, can: sorting after polyslug.canonical, N+1 without eager-loading slugs, apart() collision behavior) — but that reflects considered design, not production tenure across many teams. Treat it as a strong beta, with the v0.24.0 identity-resolution path as the least-proven surface. Internally, a platform team must own the contract tests and the on-call runbook, because "the vendor will handle it" is not available here. Procurement should be briefed early; this package will not survive a vendor-accountability gate.

Scaling. Two cost centers: storage and per-request resolution. polyslug_slugs grows as records × slugs retained × locales, and retired identities now add a lookup dimension, so retention policy for soft deletes and merges (keep vs release) is a capacity decision, not just a nicety. The default RandomTokenEncoder is leak-free but costs a store round trip per key — plan caching for hot routes, and cache stale-full-id URLs so the TailTokens enumeration runs once per distinct URL. Every list view that emits URLs must eager-load slugs (or use polyslugPreload()) or pay a query per row. Sitemaps must chunk at 50k URLs / 50 MB per file; the package provides the alternates but the sharding is ours.

Failure modes and controls. Mass 404 on an encoder swap (control: legacy_decoders, encoder decision gate). Old full-id URLs 404 after a merge because identityOf was never set (control: identity contract signed before publication, merge test in the contract suite). Unbounded TailTokens::apart() enumeration on pathological id ties (control: measure candidate counts in staging, bound via second id). Cross-tenant or draft existence leakage via unconstrained polyslugResolveQuery() (control: required override on scoped models, enforced by code review and a test per model). Wrong-language canonical 301s cached at the edge (control: verify locale source in staging, use a non-permanent status until stable, document cache purge). Redirect-inflated analytics from side effects in GET handlers (control: keep GETs side-effect-free; move counters behind middleware-excluded paths). Silent nested-route 404s from a missing ResolvesSluggableChildren (control: route-binding test per nested route). Reserved-word false positives turning slugs into api-2 (control: override polyslugReservedWords() on prefixed models).

Ramp-up. Expect a half-day spike (add a merge scenario for consolidation-heavy models), one to two sprints to a production surface for a single model type, and a real learning curve on config semantics rather than on code. Enablement needs a short internal standard: the attribute template, the required polyslugResolveQuery() override, the identity-column contract, eager-loading rules, the encoder we use and why, and a review checklist. Success metrics to instrument from day one: inbound 404 rate on previously published URLs, redirect volume by path, hreflang coverage per locale, time-to-rename for a published page, and continuity of old links after a record merge.

Recommendation. Adopt narrowly — content and catalog surfaces where locales, renames, or record consolidation are real — gated on a two-model spike, a written encoder decision, and an identity-column contract; skip for single-locale CRUD. v0.24.0 materially strengthens the package's core promise by carrying URL stability through identity changes, which widens the addressable use cases (merges, dedup, legacy-ID migration) but also widens the config surface and adds a young code path to validate. The value is real, but the dependency must be wrapped, pinned, and tested before it touches a roadmap commitment.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console