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

deligoez/laravel-model-hashids

Add short, unique Hashids to your Laravel Eloquent models. This package provides a trait that obfuscates numeric IDs for URLs and APIs, with automatic route resolution, configurable salt, length and alphabet, while keeping integer keys internally.

View on GitHub
Deep Wiki
Context7

Getting Started

deligoez/laravel-model-hashid gives Eloquent models an obfuscated public identifier derived from the primary key — no extra column, no backfill, no schema change.

Install and configure (5 minutes):

composer require deligoez/laravel-model-hashid   # v4.1.0: PHP 8.3/8.4, Laravel 11/12/13
php artisan vendor:publish --tag=hashid-config

Set HASHID_SALT in .env immediately — the shipped default is public and gives you nothing. Then:

use Deligoez\LaravelModelHashid\Traits\HasHashid;

class Invoice extends Model
{
    use HasHashid;

    public function getRouteKeyName(): string
    {
        return 'hashid';
    }
}

That's the whole first use case: route('invoices.show', $invoice) now emits /invoices/jR4bXk9… instead of /invoices/1, and Invoice::findByHashid($hash) / findByHashidOrFail($hash) replace find() in controllers.

Where to look first: the published config/hashid.php (salt, length, alphabet) and the HasHashid trait itself — it's small enough to read in one sitting and tells you exactly which query helpers and accessors exist in your installed version. Run php artisan list once to see the package's Artisan commands (re-hashing/utility commands vary by version).

Version note: v4.1.0 is a drop-in upgrade from v4.0.x — composer update only, no code changes. If you're on PHP 8.2 or Laravel ≤10, stay pinned to ^4.0.

Implementation Patterns

Route model binding is the default path. Once getRouteKeyName() returns hashid, Laravel resolves bindings through the package automatically. Nested resources need the trait on every model in the chain (/teams/{team}/invoices/{invoice}), not just the deepest one.

Controller lookups. Prefer findByHashidOrFail() over manual null checks — it gives you the standard 404 path instead of a silent null propagating into a view or API response.

Decode when you need real query constraints. hashid is a computed accessor, so where('hashid', $value) does not work — there is no column. For anything beyond a single lookup (pivots, whereIn, joins, complex filters), decode the hashid to the raw key via the package's Hashid facade/generator and constrain on whereKey(...) / whereIn('id', ...).

API resources. Cast at the boundary:

return ['id' => (string) $this->hashid, ...];

This keeps value objects out of your JSON contract and makes it obvious to future readers that the public ID is deliberate.

Validation rules. exists:invoices,hashid will fail — not a column. Either validate against the raw key after decoding, or wrap the decode in a custom rule that returns a friendly 422 rather than a 404.

Testing. Pin HASHID_SALT in phpunit.xml so hashes are deterministic across machines and CI. Then assert the important invariant directly:

$this->assertStringNotContainsString('/1', route('invoices.show', $invoice));

No migration work. Because the hashid is derived at read time, existing rows, seeders, and DB::table()->insert() bulk loads all work unchanged. That is the package's main day-to-day advantage over UUID/ULID columns.

Gotchas and Tips

Hashids are obfuscation, not encryption. They are reversible by anyone who learns your salt, and the alphabet/length are guessable from enough samples. Never treat possession of a hashid as authorization — keep policies and authorize() calls exactly where they were.

The salt is effectively immutable. Changing salt, length, or alphabet changes every hashid at once, breaking every bookmarked URL, sent email, and cached page. Decide it before launch. If you must rotate, keep the old configuration available for a transition window and re-generate any stored URLs (webhooks, exports, sitemaps) — nothing in the database needs migrating, which makes it easy to forget the external copies.

The accessor is a value object, not a string. $model->hashid === $string fails; strlen($model->hashid) and using it as an array key will bite. Cast to (string) whenever it leaves the model.

Unsaved models have no hashid. Accessing it before save() (or after forceDelete) yields nothing useful — guard your factories and form previews.

Integer keys only. UUID/ULID or composite primary keys are outside the package's model; you'll get garbage rather than an exception. Same for models where the key changes.

Same salt + same id = same hashid across models. An Invoice with id 1 and a User with id 1 produce identical hashes. Usually harmless because routes are type-scoped, but don't build a global "look up anything by hashid" router on top of it — configure distinct salts per model if your version supports it.

Installing the trait isn't enough. Forgetting getRouteKeyName() leaves IDs in URLs while everything else looks correct. This is the single most common silent misconfiguration — cover it with the route assertion above.

Invalid hashes fail quietly. Lookups return null / 404 rather than throwing, which is right for production but annoying while debugging. Log the raw incoming segment when a 404 surprises you — a truncated or URL-encoded hash is the usual culprit.

On the 4.1.0 upgrade: it's composer update and nothing else. The CI matrix runs both prefer-lowest and prefer-stable on PHP 8.3/8.4, which is a good signal, but it validates the package against lowest dependency versions — your app's own suite is still the only thing checking your real dependency graph. The Pint 1.29 changes are pure formatting; only relevant if you've published and edited package files or subclass package classes.

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.
htinlynn/genius-helper
maco-studios/hephaestus
elmogy/larafast
xeviant/laravel-paystack
gorilladash/laravel-website-sdk
martialbe/flysystem-oss
chondal/3312client
jeroen-g/flickr
opscale-co/nova-genbi
itsjustvita/laravel-retention
tequilarapido/trackit
gregsphotography/site-builder-filament
cabaoctavio/laravel-query-log
gregsphotography/site-builder-components
io-digital/clickatell
snowsoft/laravel-admin
oxygen/auth
relicsoftware/barakah-payment-gateway
tobya/work-with-composer
equipc/laravel-email-validator