kra8/laravel-snowflake
Generate distributed, time-ordered Snowflake IDs in Laravel with kra8/laravel-snowflake. It offers configurable worker and datacenter IDs, sequence handling, and an Eloquent trait, giving models unique 64-bit IDs without extra services or coordination.
Install
composer require kra8/laravel-snowflake
Laravel 11/12/13 auto-discovers the SnowflakeServiceProvider — no manual registration needed. Publishing the config is optional but recommended:
php artisan vendor:publish --tag=snowflake-config
Where to look first
config/snowflake.php — the four knobs that matter: epoch, datacenter_id, worker_id, sequence (with SNOWFLAKE_* env fallbacks).Snowflake service class and the snowflake_id() helper — the two entry points you'll actually call.snowflake:generate / snowflake:id) — handy for smoke-testing that your epoch and worker setup produce sane, monotonic IDs.First use case Generate a 64-bit, time-ordered ID in place of an auto-increment primary key:
use Kra8\Snowflake\Snowflake;
$id = app(Snowflake::class)->next(); // e.g. 1234567890123456789
// or the helper:
$id = snowflake_id();
Wire it into a model's creating hook (see patterns below) and you have a distributed, sortable PK in minutes. This release (v2.5.0) adds Laravel 13.x compatibility via #60, so the package is safe on current LTS/stable stacks; the rest is dependency hygiene (Carbon, CommonMark).
As a model primary key (most common)
protected $keyType = 'int';
public $incrementing = false;
protected static function booted(): void
{
static::creating(function ($model) {
$model->{$model->getKeyName()} ??= snowflake_id();
});
}
Assign only when unset (??=) so factories and manual overrides still work, and leave $keyType as int server-side — it's only the JSON boundary that needs care (see Gotchas).
In migrations
Schema::create('orders', function (Blueprint $table) {
$table->unsignedBigInteger('id')->primary(); // not ->id()
// ...
});
Snowflake values fit unsigned 64-bit; avoid $table->id() which assumes auto-increment.
Distributed deployment topology
Assign a unique (datacenter_id, worker_id) pair per node — that's the entire point of the algorithm. Typical setups:
SNOWFLAKE_DATACENTER_ID / SNOWFLAKE_WORKER_ID env per task definition.Keep the pair stable across restarts; changing it mid-run risks collision only if the same pair is reused, but stability keeps ID ordering sane.
Batch generation / bulk inserts Sequences are cheap — a tight loop is fine:
$ids = collect(range(1, 1000))->map(fn () => snowflake_id());
Don't parallelize across processes sharing a worker ID; the sequence counter isn't shared memory.
As route keys / API identifiers
Because IDs are time-ordered, ORDER BY id is effectively ORDER BY created_at. Use whereIn(snowflake_id()) sparingly — for lookups prefer the natural key or a hash so you don't leak creation timestamps (Snowflake IDs are trivially time-decodable).
Number.MAX_SAFE_INTEGER; cast to string in API resources ((string) $model->id) and in any JSON serialization. This is the #1 bug report for every Snowflake package.epoch once you have data. Existing IDs encode the old epoch; shifting it produces overlapping/duplicate value ranges. Pick it once and document it (default is the Twitter epoch).now() globally.(id) only within a single worker.php artisan config:cache again — stale cached config silently uses the old datacenter_id/worker_id.$incrementing = false matters — forgetting it makes Eloquent try to read insertGetId() on a manually-assigned PK and throws at insert time.composer update. If you're pinned to Laravel 11/12, staying on 2.4.1 is also fine — v2.5.0 only widens the constraint.Snowflake service in your own class rather than forking; the underlying generator is not designed to be subclassed.$this->app->instance(Snowflake::class, $fake)) so feature tests produce deterministic IDs; it's cleaner than intercepting the helper.How can I help you explore Laravel packages today?