pugx/shortid-php
Generate short, unique, URL-friendly IDs in PHP with PUGX ShortId. A lightweight, dependency-free library for non-sequential identifiers useful for URLs, tokens, and database keys.
Install with Composer:
composer require pugx/shortid-php
Then generate an ID:
use PUGX\Shortid\Shortid;
$id = Shortid::generate();
echo $id; // e.g. "racsam"
$string = (string) $id;
Where to look first
PUGX\Shortid\Shortid — the static generate() entry point; this is what you will call 90% of the time.PUGX\Shortid\Factory — configures the alphabet and the "worker id" seed used by the generator. Reach for it only when you need to customize.First realistic use case: assigning short, URL-friendly, non-sequential identifiers to Eloquent models (public URLs, invoice numbers, share tokens), or generating human-shareable keys for cache entries and short links. You typically wire it into a model or an Attribute cast rather than calling it in controllers.
Auto-assigning an ID on model creation (Eloquent trait)
namespace App\Models\Concerns;
use PUGX\Shortid\Shortid;
trait HasShortId
{
protected static function bootHasShortId(): void
{
static::creating(function ($model) {
if (empty($model->{$model->shortIdColumn()})) {
$model->{$model->shortIdColumn()} = (string) Shortid::generate(10);
}
});
}
protected function shortIdColumn(): string
{
return 'short_id';
}
}
Use it on any model:
class Invoice extends Model
{
use \App\Models\Concerns\HasShortId;
}
Custom length for collision headroom
$id = (string) Shortid::generate(12);
Pick a length based on volume. At 64 symbols, 7 chars gives ~4 trillion combinations, but the "birthday" risk becomes real in the millions of rows across a single collection. For user-facing public IDs I default to 10–12 characters.
Custom alphabet
use PUGX\Shortid\Factory;
use PUGX\Shortid\Shortid;
$factory = new Factory('23456789abcdefghijkmnpqrstuvwxyz', 1);
$id = (string) Shortid::generate(10, $factory);
Ambiguity-safe alphabets (no 0/O/1/l/I) are useful when IDs are read aloud or hand-typed.
Distributed generation with worker IDs
If you run multiple workers/containers and need the "unique without coordination" guarantee the ShortId algorithm promises, give each node a distinct worker ID and keep the factory in a service:
// AppServiceProvider::register()
$this->app->singleton(Factory::class, fn () => new Factory(null, (int) env('SHORTID_WORKER_ID', 1)));
Then inject Factory wherever you generate IDs.
Symfony users: the same vendor publishes pugx/shortid-bundle, which registers the Shortid service and exposes config for worker id/seed. If you are on Symfony, prefer the bundle rather than wiring Factory by hand.
Casting to a scalar early
Because generate() returns a Shortid value object, cast at the boundary (DB write, JSON response, URL builder) rather than letting the object leak into your domain model:
$model->code = (string) Shortid::generate(10);
return response()->json(['code' => (string) $model->code]);
Not a security token. ShortId is designed for short, non-guessable-looking identifiers, not CSRF tokens, password reset tokens, or API secrets. Historically the algorithm is built on non-cryptographic randomness; use random_bytes/Str::random for anything security-sensitive.
Uniqueness is probabilistic, not guaranteed. Always back the column with a UNIQUE index and wrap creation in a retry loop:
do {
try {
return Invoice::create(['short_id' => (string) Shortid::generate(12), ...]);
} catch (QueryException $e) { /* collide, retry */ }
} while (true);
Even then, cap the retries to avoid an infinite loop on a bad seed.
Case-insensitive storage kills entropy. MySQL's default utf8mb4_unicode_ci collation treats a and A as equal, halving your effective alphabet for uniqueness checks. Either use a binary/case-sensitive collation for the column (utf8mb4_bin) or a lowercase-only alphabet.
Default alphabet contains _ and -. Fine for URLs and cache keys, but avoid using ShortIds as-is where those characters matter (some email systems, SMS formatting, or downstream systems that strip punctuation). Ship a custom alphabet if that's a risk.
The default 7-character length may clash at scale. Increase length early. Retro-fitting longer IDs later is painful because they are already externalized.
Casting in json_encode. Unless the value object implements JsonSerializable, json_encode($shortid) will not produce a string. Always cast to (string) when serializing.
Worker ID collisions defeat the design. Two containers configured with the same worker ID will still mostly be fine due to randomness, but you lose the sequential-safety guarantees the algorithm intends. Keep worker IDs unique per process and stable across requests.
Alphabet must be unique characters. Passing a string with duplicate characters silently shrinks the effective space. Validate in your config layer.
if (strlen($alphabet) !== count(array_unique(str_split($alphabet)))) {
throw new InvalidArgumentException('Alphabet contains duplicates');
}
Debugging collisions: log the generated ID, worker id, length, and timestamp on collision. If collisions cluster in time/worker, it's a seed problem, not an alphabet problem.
Extension point: the Factory is the only real hook; subclass it if you need to inject a deterministic RNG for tests. For a project-wide policy (length, alphabet, worker id), wrap Shortid::generate() in your own ShortIdGenerator service and inject that everywhere — you get a single place to tune length and swap the implementation later.
How can I help you explore Laravel packages today?