channor/opaque-route-key
Laravel package that swaps numeric route model binding keys for opaque, non-guessable identifiers, so URLs stop exposing sequential IDs. Simple setup, works with existing route model binding, and helps keep internal IDs and resource counts private.
v2 is a breaking release of the opaque route key package — do not treat it as a drop-in minor upgrade.
composer why channor/opaque-route-key and grep your app for the legacy namespace:
grep -rn "Channor\\\\HashedRouteKey" app/ config/ database/ tests/
If you find hits and cannot migrate now, pin channor/opaque-route-key:^1.1 and defer.composer require channor/opaque-route-key:^2.0 and read docs/upgrade-v2.md before touching code — it is the canonical migration checklist for this release.hashed-route-key tag is gone). Verify the file that lands is the v2 config, not a stale config/hashed-route-key.php left in the repo.HASHED_ROUTE_KEY_SALT fallback was removed in v2 — if you relied on it, the value is now silently ignored and all previously issued keys stop resolving.php artisan config:clear (and optimize:clear on cached deploys), and hit a bound route.Where to look first: the README quick-start for the current trait/facade names, then docs/upgrade-v2.md for the namespace and config remapping table. The first real use case is almost always the same: stop leaking sequential auto-increment IDs in URLs and route() output without hand-rolling a UUID column.
Model-level opt-in. The package is designed to be applied per model via a trait, with auto_reserve_model_names kept at its false default. That means v2 does not auto-discover every model — you explicitly attach the trait and let route model binding do the resolution. A common mistake is assuming the new default reserves names for you; it doesn't, so keep the registration explicit if you depend on per-model key namespaces.
Route model binding is the primary integration point. The idiomatic flow is: trait on the model → Route::get('/invoices/{invoice}', ...) → the controller receives a hydrated model. Because the codec defaults and attempt-0 behavior are preserved in v2, keys generated before the upgrade should still round-trip (assuming an unchanged salt), which makes the migration mostly mechanical. Verify this with one known URL before rolling out.
API resources and link generation. Return the opaque key in resources rather than id, and drive all outbound URLs through route() so the key is generated consistently. If you're building URLs by string concatenation anywhere, those are the places that break silently on salt changes.
Validation. Prefer route binding over Rule::exists against the raw column — opaque keys are not database values, so an exists rule will reject valid input. Where you must validate early, validate the shape/decodability of the key rather than its presence in a table.
Migration sweep for v1 apps. Replace the legacy Channor\HashedRouteKey trait with the v2 equivalent in every model, then delete the old config/hashed-route-key.php so nothing reads a stale value by accident. Also audit composer.json for anything relying on the removed replace metadata — a package that previously satisfied a dependency through replacement will now surface as a duplicate or conflict on composer update.
Testing. Assert on decoded/bound results rather than hardcoded key strings where possible, but keep at least one golden-value test per model — it is the cheapest early warning that your salt or codec config drifted.
HASHED_ROUTE_KEY_SALT env fallback means keys regenerate from a different source unless you explicitly carry the value into the v2 config. Symptom: routes bind fine for new records, every old bookmark 404s.hashed-route-key. If your deployment only ships the old file, the package falls back to defaults that likely differ from your intended salt.php artisan config:clear runs. On containerised deploys, confirm the cached config was rebuilt in the same layer that wrote the new file.vendor:publish --tag=... with the legacy tag silently publishes nothing — no error, just a missing file.Class "Channor\HashedRouteKey" not found at boot means one model was missed in the sweep. Static analysis or a repo-wide grep catches this before deploy.auto_reserve_model_names is false. If your v1 config had it enabled via the legacy file, the behaviour quietly reverts. Confirm intent rather than assuming the old setting carried over.replace removal. If your app or a sister package depended on the replacement metadata to satisfy a constraint, resolve that explicitly before running composer update in CI — this surfaces as a dependency resolution failure, not a runtime one.^1.1 requires reinstating the old namespace and config. Decide the rollback boundary (and whether old URLs must keep working) before the production cutover.How can I help you explore Laravel packages today?