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

red-explosion/laravel-sqids

Laravel Sqids generates short, unique, obfuscated IDs from integer keys, keeping database IDs hidden in URLs and APIs. Includes route model binding, validation rules, and simple configuration for a seamless drop-in integration.

View on GitHub
Deep Wiki
Context7

Getting Started

red-explosion/laravel-sqids wraps the sqids/sqids encoder so you can turn sequential integer keys into short, non-obvious strings (and back) without touching your schema.

composer require red-explosion/laravel-sqids
php artisan vendor:publish --tag=sqids-config

First three places to look:

  1. config/sqids.php — alphabet, min_length, and blocklist. Treat the alphabet as a secret-ish key (see Gotchas).
  2. The Sqid facade (RedExplosion\Sqids\Sqids) — Sqid::encode(), Sqid::decode().
  3. The HasSqids model trait — the fastest path to value.

First use case: stop leaking /invoices/1043 in URLs. Add the trait to the model:

use RedExplosion\Sqids\Concerns\HasSqids;

class Invoice extends Model
{
    use HasSqids;
}

Route model binding now resolves /invoices/8fKq2 to the same record, and route('invoice.show', $invoice) generates sqids automatically. Zero controller changes.

Verify your install with a tinker one-liner before wiring anything up:

Sqid::encode(1);            // "8fKq2" (example)
Sqid::decode('8fKq2');      // [1]

v2.1.0 note: this release is Laravel 13.x support only — no API, config, or behaviour changes from v2.0.0. Bump the dependency, run your suite, done.

Implementation Patterns

Route model binding (the default win). The trait overrides getRouteKey()/resolveRouteBindingQuery, so {invoice} segments accept sqids while the database stays on integers. Keep getRouteKeyName() returning your real column — the trait handles the encode/decode at the boundary.

Encoding composite keys. Sqids encodes a sequence of integers, which maps neatly onto pivot/join rows or compound lookups:

Sqid::encode([$tenantId, $invoiceId]);   // opaque scoped handle
[$tenantId, $invoiceId] = Sqid::decode($handle);

This is the cleanest way to make a shareable link that is implicitly tenant-scoped — decode, then assert the decoded tenant matches the authenticated user before you query.

API resources. Keep integers internal, expose sqids at the edge:

public function toArray($request): array
{
    return [
        'id'      => Sqid::encode($this->id),
        'invoice' => Sqid::encode($this->invoice_id),
    ];
}

Resolve on the way in with a form request rule rather than manual decoding scattered through controllers.

Validation. Use the package's sqid rule for any inbound identifier so bad strings produce a 422 instead of a null-lookup 404:

$request->validate([
    'invoice' => ['required', new \RedExplosion\Sqids\Rules\SqidRule()],
]);

Where not to use the trait. Admin panels, Horizon, and internal tooling are usually better left on raw IDs — squidding everything makes debugging and support slower. Scope the trait to public-facing models.

Testing. Decode in assertions so failures stay readable ($this->assertSame($invoice->id, Sqid::decode($response->json('id'))[0])), and add one test that a raw integer in the URL 404s once you've switched a model over. If you need to keep old links alive during a migration, add a fallback resolver that accepts integers for a deprecation window — this is a per-model decision, not a global one.

Frontend handoff. Sqzids are just strings, so Inertia/Livewire props and Blade route() helpers work unchanged. Do not decode client-side and send the integer back up; send the sqid and decode server-side.

Gotchas and Tips

Sqids is obfuscation, not encryption. Anyone can decode it with your alphabet. Never use it in place of authorization — $this->authorize('view', $invoice) is still mandatory on every bound route. Likewise, don't use sqids for password resets, invite tokens, or anything needing unguessability; use Str::random, signed URLs, or UUIDs.

Changing the alphabet invalidates every existing link. There's no versioning or salt in the format. If you rotate alphabet, previously issued sqids decode to different integers or fail entirely. Decide this before launch, or store the mapping, or accept broken links. Run php artisan config:cache after any change and remember cached config will shadow .env edits — a classic "why is it still decoding the old way" moment.

decode() returns an array, always. Sqid::decode($sqid) gives [$id]; destructure or index. Invalid input yields an empty array rather than throwing, so an unchecked [0] on garbage is a null/undefined-index bug waiting to happen. Guard with the validation rule or an explicit if ($decoded === []) check.

The blocklist applies at encode time only. It prevents generating offensive strings; it does not make those strings undecodable. Don't assume blocklisted patterns are rejected on input.

min_length is padding, not security. It makes IDs uniform-length and prettier; it does not add entropy. Short sqids are still trivially enumerable by decoding neighbours — if enumeration is a real threat, you need authorization, rate limiting, or a non-sequential key, not a longer sqid.

Negative and zero-ish inputs. Plain integers only; guard against 0/negative values reaching encode() for nullable foreign keys ($this->invoice_id may be null on a draft). Encode conditionally or use the trait's binding only on models with guaranteed non-null keys.

Route binding edge cases. Soft deletes, custom primary keys, and getRouteKeyName() overrides are where the trait needs a second look — verify resolveRouteBindingQuery still applies your global scopes, and check that nested bindings (/clients/{client}/invoices/{invoice}) use the trait on both models or you'll get a mixed-convention URL that's confusing to debug.

Extension points. If you need custom encoding (per-tenant alphabets, for example), resolve Sqids from the container with your own config rather than mutating the singleton, and keep the trait's behaviour as the default so route binding stays predictable.

Upgrade hygiene for v2.1.0. No code changes required, but if you pin illuminate/* in composer.json, widen those constraints in the same commit as the package bump, or you'll get a resolver conflict that looks like a package bug. Run composer update red-explosion/laravel-sqids -W and confirm the trait + facade still resolve on your target Laravel version — the test suite covers the encode/decode contract, not your route-binding customizations.

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