haruncpi/laravel-id-generator
Laravel ID Generator creates unique, customizable IDs for database records. It supports prefixes, suffixes, fixed lengths, and sequence resets, making it easy to generate invoice, order, ticket, and other formatted IDs without manual tracking.
Install with Composer (the service provider and IdGenerator facade are auto-discovered):
composer require haruncpi/laravel-id-generator
The only entry point you need is the facade:
use Haruncpi\LaravelIdGenerator\IdGenerator;
$id = IdGenerator::generate([
'table' => 'invoices',
'field' => 'invoice_no',
'length' => 10,
'prefix' => 'INV-',
]);
// "INV-000001" (length is TOTAL characters, prefix included)
table is required; field defaults to id; length defaults to 8; prefix defaults to ''.First use case: an Invoice model with a human-readable number (INV-000123) instead of a raw auto-increment integer. Add the column as a string with a unique index, call the facade in a creating hook, and you're done.
Auto-generate on model creation — the idiomatic wiring:
protected static function booted()
{
static::creating(function (Invoice $invoice) {
if (blank($invoice->invoice_no)) {
$invoice->invoice_no = IdGenerator::generate([
'table' => $invoice->getTable(),
'field' => 'invoice_no',
'length' => 15,
'prefix' => 'INV-' . now()->format('Ymd') . '-',
]);
}
});
}
Date-scoped / per-tenant counters — change the prefix and pass 'reset_on_prefix_change' => true so the sequence restarts when the prefix (e.g. a new day or tenant) changes:
IdGenerator::generate([
'table' => 'invoices',
'field' => 'invoice_no',
'length' => 15,
'prefix' => 'INV-' . now()->format('Ymd') . '-',
'reset_on_prefix_change'=> true,
]);
Custom primary key — if you use the generated ID as the PK, tell Eloquent:
public $incrementing = false;
protected $keyType = 'string';
protected $primaryKey = 'invoice_no';
Centralize definitions in a helper so the table/field/length/prefix triple isn't duplicated across seeders, factories, tests and model hooks:
class Ids {
public static function invoice(): string
{
return IdGenerator::generate([
'table' => 'invoices', 'field' => 'invoice_no',
'length' => 15, 'prefix' => 'INV-' . now()->format('Ymd') . '-',
]);
}
}
Then reference Ids::invoice() from the model, a factory, or a service that imports records.
Retry wrapper for uniqueness — combine with a unique index and catch the duplicate:
DB::transaction(function () use (&$invoice) {
$invoice->invoice_no = Ids::invoice();
$invoice->save(); // unique index guarantees the final word
}, 3); // Laravel retries on deadlock, not on unique violations — wrap manually
lockForUpdate or dedicated counter table. Two parallel requests can compute the same ID. Always back the column with a unique index and handle 1252/23000 by regenerating, or serialize generation inside a transaction with row locking.length is total width, not the numeric part. length: 10 with prefix INV- yields six digits. If your prefix grows (dates, tenant codes) and length doesn't, the number silently overflows and IDs get longer than expected.MAX(field). Because counting is done on the string column, mixing prefixes or lengths (e.g. a manual INV-9 and generated INV-000010) can produce a surprising "next" value. Keep one prefix format per table.MAX() on a heavily written table is also slow as the table grows — consider generating from a dedicated id_sequences table if volume is high.000001. Soft-deleted rows still count toward MAX(), which is usually what you want (no ID reuse) but can surprise people expecting a "reset".RefreshDatabase tests, factories call Ids::invoice() and hit the real DB — wrap seeding in a transaction so IDs stay predictable, or pass an explicit ID in the factory for assertion stability.Ids/IdGeneratorService class; that's also where you'd swap in a DB-backed counter or Redis::incr if you later outgrow the package.How can I help you explore Laravel packages today?