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

Laravel Hashid Laravel Package

elfsundae/laravel-hashid

Laravel Hashids turns auto-increment integers into short, unique, non-sequential strings for safe public IDs in URLs. Includes Eloquent route binding, a facade, validation rules, and configurable salt, length, and alphabet.

View on GitHub
Deep Wiki
Context7

Getting Started

1. Install and confirm auto-discovery

composer require elfsundae/laravel-hashid

On Laravel 5.5+ the service provider and Hashid facade are auto-discovered. If you're on an older version, register Elfsundae\LaravelHashid\HashidServiceProvider and alias Elfsundae\LaravelHashid\Facades\Hashid.

2. Publish and review the config

php artisan vendor:publish --provider="Elfsundae\LaravelHashid\HashidServiceProvider"

Open config/hashid.php first. It defines a default connection plus a connections map. Each connection controls the salt, minimum hash length, and alphabet. Treat this file as the source of truth for every hash your app emits.

3. Set a real salt

HASHID_SALT=some-long-random-per-app-secret

Do not ship the empty/default salt. The salt is the only thing standing between a hash and the original integer.

4. First use

use Elfsundae\LaravelHashid\Facades\Hashid;

$hash = Hashid::encode(42);      // e.g. "kM"
$ids  = Hashid::decode($hash);   // e.g. [42] — hashids decode returns an array

$id = $ids[0] ?? null;

Or via the helper:

hashid()->encode(42);
hashid('orders')->encode(42);    // named connection

Your first real use case should be an API resource or model accessor that swaps a sequential primary key for a hashid in responses.

Implementation Patterns

Presentation-layer encoding with API resources

Keep database keys as integers; encode only at the boundary.

class OrderResource extends JsonResource
{
    public function toArray($request): array
    {
        return [
            'id'         => hashid('orders')->encode($this->id),
            'created_at' => $this->created_at,
        ];
    }
}

Model accessor for URLs and Blade

class Order extends Model
{
    protected $appends = ['hashid'];

    public function getHashidAttribute(): ?string
    {
        return $this->exists ? hashid('orders')->encode($this->getKey()) : null;
    }
}

Append only when needed — it adds work to every serialization.

Route model binding

Implicit binding matches the raw key, so override it to decode first:

public function resolveRouteBinding($value, $field = null)
{
    $decoded = hashid('orders')->decode($value);
    abort_if(empty($decoded), 404);

    return $this->where($field ?? $this->getRouteKeyName(), $decoded[0])
                ->firstOrFail();
}

Then route with {order} and receive hashed strings in URLs without changing controller code.

Validation rule for incoming hashes

Validator::extend('hashid', fn ($attribute, $value) =>
    ! empty(hashid()->decode($value))
);

$request->validate([
    'order_id' => ['required', 'hashid'],
]);

Or normalize in a form request:

protected function prepareForValidation()
{
    $decoded = hashid()->decode($this->input('order_id'));
    $this->merge(['order_id' => $decoded[0] ?? null]);
}

One connection per domain

Use separate salts for different entity types so an order hash can't accidentally resolve as a user ID:

'connections' => [
    'main'   => ['salt' => env('HASHID_SALT'),         'length' => 8],
    'orders' => ['salt' => env('HASHID_ORDERS_SALT'),  'length' => 8],
    'users'  => ['salt' => env('HASHID_USERS_SALT'),   'length' => 8],
],

UUIDs and hex values

Hashids operates on integers. For UUIDs, strip dashes and use the hex API:

$hash = Hashid::encodeHex(str_replace('-', '', $uuid));
$hex  = Hashid::decodeHex($hash);
$uuid = vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split($hex, 4));

Middleware for route-prefix decoding

If many routes carry hashed IDs, a middleware that decodes known route parameters (and 404s on failure) keeps controllers clean:

public function handle($request, Closure $next)
{
    foreach (['order', 'user'] as $param) {
        if ($value = $request->route($param)) {
            $decoded = hashid()->decode($value);
            abort_if(empty($decoded), 404);
            $request->route()->setParameter($param, $decoded[0]);
        }
    }

    return $next($request);
}

Testing

Prefer round-trip assertions over hard-coded hashes, since changing salt or length changes every output:

$this->assertSame(42, hashid()->decode(hashid()->encode(42))[0]);
$this->assertEmpty(hashid()->decode('not-a-real-hash'));

Gotchas and Tips

  • Reversible is not encryption. Anyone with the salt can decode every ID. Hashids hides sequence and volume, not data. Always keep authorization checks on the decoded integer.
  • The salt is a one-way door. Changing it — or the alphabet, or the minimum length — invalidates every previously issued hash. Store it in the environment, back it up, and plan a migration before rotating. Published URLs, emails, and webhooks will break otherwise.
  • decode() returns an array (hashids semantics). Always index [0] and guard with empty()/??. A tampered or foreign hash usually yields [], not an exception.
  • Beware double encoding. Encoding in a model accessor and again in an API resource produces a hash of a hash. Pick one boundary — usually the resource or the accessor, not both.
  • Minimum length is padding, not entropy. length => 8 makes hashes look uniform; it does not make them harder to reverse. Security comes from salt secrecy.
  • Alphabet rules. The alphabet must contain at least 16 unique characters. Avoid characters that require URL encoding; the default alphanumeric set is already URL-safe. Removing look-alike characters (0/O, 1/l) is fine, but changing the alphabet later breaks existing hashes.
  • Integers only. Very large IDs beyond PHP_INT_MAX and UUIDs need encodeHex/decodeHex.
  • Invalid route bindings should 404, not 500. If you decode in a controller and pass null to findOrFail, you'll get the right status; if you cast blindly, you'll get a type error.
  • Config caching. After editing config/hashid.php or .env, run php artisan config:clear (and config:cache in production). Stale cached config with an old salt is a classic "why did all my links break" bug.
  • Don't use it for secrets. Passwords, API tokens, signed URLs, and password-reset tokens need real cryptography (encrypt, Hash::make, signed URLs). Hashids is obfuscation.
  • Hashids are case-sensitive. Normalize input carefully; lowercasing a hash before decoding will fail silently.
  • Cross-connection collisions are possible by design. The same integer under two different salts yields two different hashes — good. But a hash from connection A will generally fail to decode under connection B, so don't mix them in one field.
  • Logs and analytics still leak. Hashids only protect IDs you actually encode. Sequential IDs in logs, queue payloads, admin panels, and database dumps remain visible unless you encode there too.
  • Extension points. The manager resolves connections from config; you can bind a custom manager or connection in the container if you need tenant-specific salts (e.g., resolve the salt per request before the first encode). Cache resolved connections in long-running workers (Octane, queues) if you override the default binding.
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.
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core