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 Hashids Laravel Package

bvtterfly/laravel-hashids

A Laravel wrapper for Hashids that encodes numeric IDs into short, unique, obfuscated strings, keeping database keys hidden in URLs and APIs. It offers simple facades, helpers, and config for salts, lengths, and custom alphabets.

View on GitHub
Deep Wiki
Context7

Getting Started

Install the package and publish its config before writing any encode/decode logic — the salt defaults are intentionally useless for production, so config is step one, not an afterthought.

composer require bvtterfly/laravel-hashids
php artisan vendor:publish --tag=hashids-config

The config file (config/hashids.php) is where every meaningful decision lives: salt, min_length, and alphabet, plus the connections map for multiple settings. Read this file first — it's the entire API surface area you'll touch day-to-day. Set HASHIDS_SALT from env, pick a min_length that comfortably exceeds your ID digit count (8–12 is a sensible floor), and leave alphabet alone unless you have a specific reason to exclude characters.

The facade (typically Hashids::connection(...)) is what you'll call from controllers and models. Confirm it's registered by running php artisan about or dumping bootstrap/cache/packages.php — Laravel's auto-discovery handles this on modern versions, so no config/app.php edits are needed.

First use case: hide a primary key in a URL. In a controller:

$hash = Hashids::encode($user->id);           // "k5x9..."
$id   = Hashids::decode($hash)[0] ?? null;    // 42

Then validate the round-trip in tinker before wiring it into routes. That single encode/decode pair confirms the provider, config merge, salt, and alphabet are all working — which is where 90% of setup problems surface.

Implementation Patterns

Route model binding via getRouteKey(). This is the highest-leverage pattern: override getRouteKey() on the model and Laravel will encode on generation and decode on lookup automatically.

class User extends Model
{
    public function getRouteKey(): string
    {
        return Hashids::encode($this->getKey());
    }

    public function resolveRouteBinding($value, $field = null): ?self
    {
        $id = Hashids::decode($value)[0] ?? null;
        return $id ? $this->whereKey($id)->first() : null;
    }
}

route('users.show', $user) now produces /users/k5x9... and UserController@show receives the decoded model with zero manual decoding. Centralize this in a HasHashidRouteKey trait so you apply it consistently and change it in one place later.

API resources. Encode at the boundary, not in your models' core attribute logic. Put the encode in toArray() of the resource class so the raw integer never leaves the database layer:

public function toArray($request): array
{
    return [
        'id'         => Hashids::encode($this->id),
        // internal relations that don't leak IDs stay raw
    ];
}

Form requests and inbound ID validation. Decoding returns an array and can fail. Wrap it in a rule or a small helper so controllers stay clean:

// In a FormRequest's rules() — validate it *is* a valid hashid first.
'user_id' => ['required', 'string', function ($attr, $value, $fail) {
    $decoded = Hashids::decode($value);
    if (empty($decoded)) {
        $fail('Invalid identifier.');
    }
}],

A dedicated ValidHashid rule keeps this reusable across jobs, controllers, and console commands.

Multiple connections for multi-tenant or multi-domain apps. Use the connections map when different tenants, markets, or resource families need independent salts and lengths — e.g. one connection for customer-facing URLs and another for admin IDs so they can't be cross-forged. Pass the connection name explicitly everywhere; never rely on the default once you've introduced more than one.

Hashids::connection('admin')->encode($id);

Wrapping in an adapter for anything nontrivial. Even though the package is small, define an IdObfuscator contract with encode(int): string / decode(string): ?int and bind it to a Hashids implementation. Call sites depend on your interface; when (not if) you replace Hashids — see the archived status below — you change one class. This is the single most important pattern given the package's maintenance situation.

Jobs, commands, and events. When you dispatch a job with a hashed ID, decode before dispatch and pass the integer through the queue. Jobs should receive canonical data, not presentation-layer encodings — otherwise a config change invalidates jobs that are already queued.

Gotchas and Tips

  • The package is archived. Treat this as "stable but frozen." The last release was March 2023 and it won't get Laravel 11/12 compatibility fixes. Use it where the encoded form is a presentation concern (URL cosmetics), and keep the adapter pattern above so a swap to vinkla/hashids directly or a hand-rolled Hashids\Hashids wrapper is a few hours' work, not a refactor.

  • Hashids is obfuscation, not security. Salted hashes are reversible by anyone who gets the salt; the string is shorter and uglier than the ID, not a secret. Never use a hashid as a bearer token or as the sole authorization check — always look up the row and check permissions after decode.

  • Rotating the salt breaks every existing URL. There is no migration path from one salt to another; old hashes become garbage. Version your salt (e.g. HASHIDS_SALT_V2) and keep the old connection around during a transition if you must rotate, or don't rotate during business hours.

  • decode() never throws — it returns []. Hashids::decode('garbage') returns an empty array, and [][0] is a PHP warning, not an exception. Always guard with ?? null or array_first(); a missing guard will happily 200 with a null model and a confusing Attempt to read property on null.

  • min_length is a floor, not a guarantee. Two IDs can produce strings of very different lengths once you exceed the floor. If you need fixed-width IDs (e.g. for regex validation in routes), enforce that in a custom route constraint or validate by round-trip decode rather than by pattern.

  • Alphabet collisions across connections. Different connections with overlapping salts and alphabets can — theoretically — produce strings that decode on the wrong connection. If cross-tenant URL forging is a threat model for you, make the salt and alphabet per-tenant and validate the decode result against the tenant's data, not just !== null.

  • Config caching is the #1 "it worked yesterday" cause. Run php artisan config:clear after touching config/hashids.php locally; ensure config:cache runs during deploy. If a hash suddenly changes in staging after a deploy, check the cached config was rebuilt before debugging the package.

  • Don't encode in observers or accessors by default. An accessor that returns the encoded ID silently changes the type of $user->id everywhere — including whereKey(), foreign keys, and JSON serialization — which cascades into breakage. Keep the raw integer accessible (e.g. getRawOriginal('id')) and encode explicitly at output boundaries.

  • Debugging a "wrong" ID. If Hashids::decode() returns a number that doesn't match the record, the most common causes are (a) the wrong connection name, (b) a stale config:cache, or (c) someone edited the salt for one environment but not the other. Isolate by encoding a known ID and round-tripping — if that fails, it's config; if it succeeds but lookups fail, it's the decode-and-query logic.

  • Extension points to prefer. The facade and container binding are the sanctioned seam. Don't subclass or monkey-patch vendor code — the next composer update erases it, and on an archived package you'll have no upstream to report the breakage to anyway.

  • Lock and monitor the dependency. Pin exactly (1.1.0) in composer.json, commit composer.lock, and add a Dependabot/upstream-watch note: when a maintained fork or a Laravel-native alternative takes over, you want to know before the next major Laravel upgrade forces the issue.

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.
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
nodisrupt/laravel-agent
sierratecnologia/media-manager