netbull/hashids-bundle
A Symfony bundle that wraps Hashids to turn integer IDs into short, unique, non-sequential hashes, so you can hide database IDs in URLs. Provides container integration and configuration for encoding and decoding IDs through a simple service.
Know what you're installing. netbull/hashids-bundle is a Symfony bundle that wires the hashids/hashids library into the Symfony DI container. As a Laravel developer, the bundle's registration, config tree, and service aliases do not apply to you — there is no HashidsServiceProvider. You have two realistic paths:
composer require hashids/hashids (or vinkla/hashids, which is the Laravel wrapper for the same library) and bind it in a service provider. This is what most Laravel teams should do.If you are in a Symfony codebase (or a mixed one), the minimal steps are:
composer require netbull/hashids-bundle
config/bundles.php.DependencyInjection/Configuration.php and Resources/config/services.yaml to learn the exact config key, service ID, and whether it exposes one hasher or named hashers.salt and min_hash_length in config/packages/*.yaml, sourced from env (HASHIDS_SALT), never hard-coded.First use case: stop leaking sequential primary keys in URLs. /orders/1042 becomes /orders/jR4pQm. Inject the hasher, encode($order->getId()) when building links, decode($segment) in a controller or param converter.
Keep the integer PK, add a public hash. Don't replace auto-increment PKs with hashes in the database. Store the bigint, expose the hash as a derived accessor / API field. This keeps indexes, foreign keys, and joins sane, and lets you rotate the salt without a data migration (at the cost of breaking old links — see Gotchas).
// encode on the way out
$payload = ['id' => $hasher->encode($order->getId())];
// decode on the way in, before route model binding
$ids = $hasher->decode($segment); // [] on failure
abort_if(empty($ids), 404);
$order = Order::findOrFail($ids[0]);
Wrap it, don't sprinkle it. Inject hashids/hashids into a small PublicId service (encodeId, decodeId, decodeOrFail). The bundle's service is a thin adapter; your wrapper is where you enforce "decode returns exactly one positive int or throws" and where you can swap implementations later.
Composite keys are a feature. encode($tenantId, $orderId) produces a single opaque token; decode() gives you both back. This is the cleanest way to make IDs tenant-scoped in URLs without a separate lookup table — just always validate that the decoded tenant matches the authenticated user's tenant.
Multiple contexts. If the bundle only registers one hasher, or you need different salts per entity type (users vs. invoices vs. invite tokens), register additional Hashids instances manually with distinct salts/min lengths rather than reusing one global salt. One leaked salt should not de-anonymise every model at once.
API resources. Emit only the hash in id fields, and treat decode failure as a 404/422, not a 500. Laravel's Route::bind or a custom resolveRouteBinding is the natural integration point.
Config per environment. Salt from env(), min length fixed per app. Hashids config is cheap to construct, so don't cache the instance manually — let the container/singleton binding handle it.
hashids/hashids library is MIT — for most commercial Laravel work, use the library (or vinkla/hashids) and avoid the bundle entirely. Verify with counsel before shipping.decode() returns [], it does not throw. Every call site must handle the empty case. Wrap it once (decodeOrFail) and ban raw decode() in code review.min_hash_length is a floor, not a target. Output grows with the input number; don't assume a fixed column width or regex {n} in routes. Validate the charset instead."netbull/hashids-bundle": "1.*"), and be prepared to inline the ~50 lines of DI config it saves you.debug:container | grep -i hashid to confirm the service ID; in a plain Laravel test, dump($hasher->encode(123)) and round-trip decode in a unit test with the production salt to catch config drift early.How can I help you explore Laravel packages today?