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.
What this package actually is: a thin Laravel service-provider/facade wrapper around the hashids PHP library. Its job is to turn integer primary keys into short, opaque, reversible strings (/orders/x7Kq2 instead of /orders/1042) and back again. Everything below follows from that one sentence, because the properties a TPM cares about — reversibility, secrecy, durability — are determined by Hashids itself, not by this wrapper.
Architecture fit: Narrow and well-scoped. It sits at the HTTP presentation boundary (route-model binding, URL generation, API resource serialisation) and touches nothing else in the stack. That is the good kind of dependency: shallow, substitutable, one call site per usage. The architectural risk is not in the code shape but in the intent — teams routinely adopt "obfuscate IDs" as an implicit security control. It is not one. Hashids is a reversible encoding keyed by a salt; anyone who obtains the salt can decode every ID you have ever issued, and anyone who guesses the alphabet and minimum length has a much cheaper brute-force surface than with a 64-bit random ID. This package must be framed in the design docs as a cosmetic/anti-scraping measure, with authorisation enforced separately and rate limiting in front of any endpoint that will decode a user-supplied string.
Integration feasibility: Technically trivial — one Composer require, config publish, auto-discovered provider. Organisationally harder, for one reason that dominates everything else: the repository is archived and the last release was 2023-03-05 (v1.1.0, adding PHP 8.2 / Laravel 10). 28 stars and a score of 0.23 confirm this is a low-traffic package with no meaningful community. An archived package cannot ship a Laravel 11/12 compatibility bump, cannot patch a vulnerability, and will not accept your PR. Feasibility is therefore not "can we install it" (yes) but "are we willing to own it" (a decision, not a spike).
Technical risk:
minLength deliberately; treat "short" as a UX choice with a correctness floor.Key questions:
.env? Has anyone checked whether it already appears in log output?hashids/hashids) cheaper than the fork liability?Stack fit: Good, provided the placement is right. The package belongs in one adapter, not scattered. Concretely: a first-party interface such as PublicIdEncoder with encode(int $id): string / decode(string $publicId): ?int, implemented by a class that wraps this package. Controllers, API resources, and route-model binding depend on the interface only. If we fork later — which, given archived status, is a when rather than an if — we change one bind() in one service provider and the application is untouched. Everything below assumes that adapter exists.
Migration path (there is nothing to migrate from, so this is really an adoption path):
minLength chosen deliberately (8+ is a reasonable floor for a public-facing surface); salt generated from a CSPRNG; config values read via config(), never env() at runtime.Compatibility: Two things to verify in composer.json, cheaply, before anything else — the php constraint and the illuminate/* range. If they are ^10.0-only, this package is a hard gate on your Laravel 11 upgrade and must be scoped as such in the roadmap, not discovered during it. Also confirm behaviour on a clean install, not just composer update on an existing tree — archived packages often fail only in the fresh-install path.
Sequencing (TPM view): Do the adapter and the one-surface pilot now, while the package is still installable on your current framework. The alternative — deferring to "when we do Laravel 11" — bundles a framework upgrade with an abandoned dependency, and the two failure modes are indistinguishable during debugging. If the framework upgrade is within one or two quarters, the honest sequencing is: adopt nothing, build the adapter with a plain in-house Hashids implementation instead, and skip the abandoned wrapper entirely.
Maintenance: The package is archived, so upstream maintenance is zero by definition — not "slow", not "at risk", ended. Every future fix is ours. The practical consequences are: no framework-compat releases, no security patches, no upstream bug confirmation. If we adopt it, we are adopting a small piece of source code we now maintain. Price that in: it is perhaps 150 lines plus a config file, and forking it into a first-party package is a day's work — which also happens to be the cheapest mitigation available and probably the right shape of decision regardless. The MIT licence permits it.
Support: None. There is no maintainer to escalate to, no SLA, no security contact, no issue triage — verified archived repositories take no issues or PRs. Our support obligations therefore become fully internal, and any incident caused by the salt, the alphabet, or a decode edge case lands on our on-call, not theirs. Make sure whoever accepts that risk knows they are accepting it.
Scaling: Runtime cost is negligible — Hashids is pure arithmetic and string manipulation, microseconds per call, no I/O, no state. Cache it in an accessor if you call it in a loop, but it is not a performance concern. The real scaling concerns are two:
Failure modes (ranked by how badly they hurt):
.env, and a detection story (rate-limit anomalies on decode endpoints) because you will not get a clean alert.if ($encoder->decode($id) === $record->id) and treats it as access control. It is not — it is a lookup, not a permission check. Any endpoint that decodes an arbitrary string must still check ownership and authorise the caller.minLength down to 4 because the URLs look nicer. Short hashes mean a smaller output space, which makes enumeration cheap even without the salt. Treat config changes here as schema changes: reviewed, tested, never incidental.Ramp-up: Low for engineers — the API is two method calls and the config is three keys; a competent Laravel developer is productive in under an hour. The ramp-up that actually costs money is product and security alignment, not engineering: agreeing the durability contract for public URLs, agreeing what this does and does not protect against, and agreeing who owns the fork. Budget half a day of TPM/security/eng time for that conversation before any code is written. The engineering is a sprint task; the decision is the deliverable. Until that conversation happens, treat this package as acceptable for a contained, internal, low-consequence surface behind a first-party adapter — and unacceptable as a load-bearing identifier scheme for customer-facing permalinks, which is the opposite of what most teams reach for it for.
How can I help you explore Laravel packages today?