PHPackages                             channor/hashed-route-key - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Utility &amp; Helpers](/categories/utility)
4. /
5. channor/hashed-route-key

ActiveLibrary[Utility &amp; Helpers](/categories/utility)

channor/hashed-route-key
========================

Deterministic hashed route keys for integer IDs with keyed integrity checks.

v1.0.1(2mo ago)02↓100%MITPHPPHP ^8.2CI passing

Since Mar 8Pushed 2mo agoCompare

[ Source](https://github.com/channor/hashed-route-key)[ Packagist](https://packagist.org/packages/channor/hashed-route-key)[ Docs](https://github.com/channor/hashed-route-key)[ RSS](/packages/channor-hashed-route-key/feed)WikiDiscussions main Synced 1mo ago

READMEChangelog (2)Dependencies (7)Versions (3)Used By (0)

Hashed Route Key
================

[](#hashed-route-key)

Deterministic, model-level hashed route keys for Laravel.

Replaces sequential integer IDs in URLs with compact, opaque, HMAC-verified hashes without storing anything extra in the database.

```
/teams/3             -> /teams/kX9mG7
/teams/3/members/42  -> /teams/kX9mG7/members/bR4nYp2w

```

The core codec is plain PHP. The trait, command, and service provider require Laravel.

Why this package exists
-----------------------

[](#why-this-package-exists)

This package was built for Laravel apps that want hashed route keys to be a model-level concern. The trait-based integration keeps route-key generation and route binding on the model itself, while deriving distinct salts per model so the same integer ID produces different route keys across different models by default.

Quick start
-----------

[](#quick-start)

Add the trait to an Eloquent model with an integer primary key:

```
use Channor\HashedRouteKey\UsesHashedRouteKey;

class Team extends Model
{
    use UsesHashedRouteKey;
}
```

That is enough for route model binding and `route()` URL generation to use the hashed key. Serialization includes the computed route key by default, and can be customized or disabled.

How it works
------------

[](#how-it-works)

1. Encodes `id + offset` as a base-62 payload using a salt-shuffled alphabet.
2. Appends a keyed HMAC-based check tag.
3. Verifies the check tag before decoding.

The result is deterministic and reversible, while rejecting nearly all cross-salt, cross-model, or tampered hashes.

Installation
------------

[](#installation)

Supported targets:

- PHP 8.2, 8.3, and 8.4
- Laravel 11 and 12

Laravel package discovery will register the service provider automatically.

If your application disables package discovery, register the provider manually in `bootstrap/providers.php`:

```
return [
    App\Providers\AppServiceProvider::class,
    Channor\HashedRouteKey\HashedRouteKeyServiceProvider::class,
];
```

Publish config:

```
php artisan vendor:publish --tag=hashed-route-key-config
```

Stability warning
-----------------

[](#stability-warning)

Once URLs are public, the following settings must stay stable per model. Changing any of them invalidates all existing hashed URLs for that model:

- salt base: `config('hashed-route-key.salt')`
- salt suffix: `routeKeySaltSuffix()`
- payload length: `routeKeyMinPayloadLength()`
- check length: `routeKeyCheckLength()`
- offset multiplier: `routeKeyOffsetMultiplier()`

The contract test generator below exists to catch accidental changes to these values.

Contract test generator
-----------------------

[](#contract-test-generator)

The package ships with an Artisan command that generates stable app-level contract tests for models using `UsesHashedRouteKey`:

```
php artisan route-key:generate-test --class=User
php artisan route-key:generate-test --class=App\\Models\\Project --force
php artisan route-key:generate-test --all
php artisan route-key:generate-test --all --namespace=App\\Domain\\People\\Models
php artisan route-key:generate-test --all --namespace=Domain\\People\\Models --model-path=src/Domain/People/Models
```

The command:

- resolves common model inputs such as `User`, `Users`, or a fully qualified class name
- can scan all models using the trait with `--all`
- verifies that the model uses `UsesHashedRouteKey`
- writes strategy assertions and fixed-output hash assertions using a fixed salt base

By default, generated tests are written to `tests/Feature/RouteKeys`. Use `--path=` to write them elsewhere.

For `--all`, the default discovery scope is:

- `--namespace=App\\Models`
- `--model-path=app/Models`

If you pass a namespace under `App\\...` and omit `--model-path`, the command infers the path from that namespace. For namespaces outside `App\\...`, provide both `--namespace` and `--model-path`.

Generated tests extend `Tests\TestCase` by default, which matches the standard Laravel application test base class convention.

Per-model overrides
-------------------

[](#per-model-overrides)

Override any strategy method on the model when the defaults do not fit:

```
class Account extends Model
{
    use UsesHashedRouteKey;

    protected function routeKeyMinPayloadLength(): int
    {
        return 2;
    }

    protected function routeKeyCheckLength(): int
    {
        return 3;
    }

    protected function routeKeyOffsetMultiplier(): int
    {
        return 2;
    }

    protected function routeKeySaltSuffix(): string
    {
        return 'account';
    }
}
```

Salt derivation
---------------

[](#salt-derivation)

By default, the trait builds the codec salt from:

1. `config('hashed-route-key.salt')`
2. `routeKeySaltSuffix()`, which defaults to `snake_case(class_basename(Model::class))`

For example, `App\Models\Project` uses a salt shaped like:

```
config('hashed-route-key.salt').':project'
```

The default config uses `HASHED_ROUTE_KEY_SALT` and falls back to `APP_KEY`, so changing either changes all emitted hashes for all models using the trait unless you keep the effective salt stable.

If you need a custom or shared model suffix, override `routeKeySaltSuffix()` on that model. Keep the suffix stable once URLs are public.

Appending `route_key`
---------------------

[](#appending-route_key)

The trait can append a computed route-key attribute during array / JSON serialization.

Global config:

```
return [
    'append_route_key' => true,
    'default_attribute_name' => 'route_key',
];
```

Per-model overrides:

```
protected bool|string $appendRouteKey = false;
```

```
protected bool|string $appendRouteKey = 'hashed_key';
```

You can also override the method directly when you need custom logic:

```
public function appendRouteKey(): bool|string
{
    return $this->is_public ? 'route_key' : false;
}
```

Behavior on decode failure
--------------------------

[](#behavior-on-decode-failure)

When a hashed key is invalid, tampered, wrong-model, or malformed:

- `HashedRouteKeyCodec::decode()` returns `null`
- route model binding queries `WHERE id = -1`, which matches nothing and results in a `404`
- `$model->route_key` returns `null` on unsaved models

Core codec
----------

[](#core-codec)

The codec has no framework dependencies and can be used outside Laravel:

```
use Channor\HashedRouteKey\HashedRouteKeyCodec;

$codec = new HashedRouteKeyCodec(
    salt: 'my-secret-salt',
    minPayloadLength: 3,
    checkLength: 4,
    offsetMultiplier: 1,
);

$hash = $codec->encode(42);
$id = $codec->decode($hash); // 42
```

Config reference
----------------

[](#config-reference)

```
return [
    'salt' => env('HASHED_ROUTE_KEY_SALT', env('APP_KEY')),
    'append_route_key' => true,
    'default_attribute_name' => 'route_key',
    'min_payload_length' => 3,
    'check_length' => 4,
    'offset_multiplier' => 1,
];
```

KeyPurposeDefault`salt`Base salt for all hash derivations`APP_KEY``append_route_key`Auto-append `route_key` during serialization`true``default_attribute_name`Attribute name when appending`route_key``min_payload_length`Minimum encoded payload characters`3``check_length`HMAC check tag characters (max 32)`4``offset_multiplier`Shifts encoding space for low IDs`1`Capacity math (base-62)
-----------------------

[](#capacity-math-base-62)

Payload lengthUnique values`p=2`3,844`p=3`238,328`p=4`14,776,336The payload starts at `min_payload_length` characters and adds characters as IDs grow beyond what the current length can represent. `offsetMultiplier` shifts where these length boundaries fall.

Why this codec exists
---------------------

[](#why-this-codec-exists)

The codec in this package is intentionally narrow. Its job is to produce compact, deterministic route keys for integer model IDs with integrity checks and predictable growth characteristics. It is not intended as a general-purpose hashing framework or a statement against existing codec packages.

If another package fits your application better, use that. This package exists to support a model-centric Laravel route-key workflow with per-model salt separation and minimal operational surface area.

Alternatives
------------

[](#alternatives)

[`vinkla/laravel-hashids`](https://github.com/vinkla/laravel-hashids) and [`cybercog/laravel-optimus`](https://github.com/cybercog/laravel-optimus) are relevant alternatives for Laravel applications that want obfuscated identifiers. Either may be a better fit depending on your needs and existing conventions.

Notes
-----

[](#notes)

- This is obfuscation with integrity checks, not encryption.
- This package is not a security boundary and does not replace authentication, authorization, or signed URLs.
- Only non-negative integer IDs are supported.

Development note
----------------

[](#development-note)

This package was developed with LLM assistance. Final design, review, and release decisions remain with the maintainer.

###  Health Score

39

—

LowBetter than 85% of packages

Maintenance94

Actively maintained with recent releases

Popularity3

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity47

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~0 days

Total

2

Last Release

62d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/9cf265f56e00ef71b1393f49d5c991f7a5aaffdb5aa62eb6690e04a33a85ecd8?d=identicon)[chris-andre](/maintainers/chris-andre)

---

Top Contributors

[![channor](https://avatars.githubusercontent.com/u/11814795?v=4)](https://github.com/channor "channor (6 commits)")

---

Tags

hashidslaravelobfuscationphproute-keylaravelhashidsobfuscationRoute Keyhashed-route-key

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan

Code StyleLaravel Pint

Type Coverage Yes

### Embed Badge

![Health badge](/badges/channor-hashed-route-key/health.svg)

```
[![Health](https://phpackages.com/badges/channor-hashed-route-key/health.svg)](https://phpackages.com/packages/channor-hashed-route-key)
```

###  Alternatives

[barryvdh/laravel-ide-helper

Laravel IDE Helper, generates correct PHPDocs for all Facade classes, to improve auto-completion.

14.9k123.0M683](/packages/barryvdh-laravel-ide-helper)[spatie/laravel-enum

Laravel Enum support

3655.4M31](/packages/spatie-laravel-enum)[zonneplan/laravel-module-loader

Module loader for Laravel

24118.4k](/packages/zonneplan-laravel-module-loader)[tehwave/laravel-achievements

Simple, elegant Achievements the Laravel way

7012.8k](/packages/tehwave-laravel-achievements)[interaction-design-foundation/laravel-geoip

Support for multiple Geographical Location services.

17221.0k3](/packages/interaction-design-foundation-laravel-geoip)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
