marjose123/laravel-autonumber
A Laravel package for generating automatic sequential numbers, such as invoice or document codes, for your Eloquent models. Simple to configure and use, it helps keep numbering consistent without manual tracking.
v1.0.1 is the first release with Laravel 10 support and introduces a configurable starting value for a counter, which makes the package viable for projects that already have a numbering history. Both changes matter on day one: confirm your app is on Laravel 10 (this release raises the framework constraint — if you are still on Laravel 9, pin ^1.0.0 or upgrade the app first), then install:
composer require marjose123/laravel-autonumber
php artisan vendor:publish --tag=autonumber-config # confirm tag in README
php artisan migrate # if the package ships a counter table
The first thing to look at is the published config file: it is where the document "types" (invoice, quotation, receipt, PO) and their prefix/suffix/padding live, and in v1.0.1 it is also where you now set the starting number for each sequence. Read the config before writing any code — most of the package's behaviour is configuration, not API.
The canonical first use case is an invoice number:
$invoiceNo = AutoNumber::generate('invoice'); // e.g. INV-000001
Wire it into the model that actually needs the number (Invoice) rather than calling it ad hoc from controllers, so every creation path gets a number and none can accidentally skip it. Confirm the exact method signature against the README for this version — it is a small package and the call surface has changed between releases.
Generate on model creation, not in the controller. Put the call in the model's creating hook or a dedicated observer. This guarantees every path (HTTP, console command, seeder, imported job) produces a number, and it keeps the generation call in exactly one place when the package's API shifts:
protected static function booted(): void
{
static::creating(function (self $model) {
$model->number ??= AutoNumber::generate('invoice');
});
}
The ??= matters: it lets a seeder or a data-migration explicitly set a historical number without the package overwriting it.
Wrap generation in the same transaction as the insert. A number you generated but never persisted is a wasted sequence value — or worse, a duplicate if the counter store is transactional and the insert rolls back. Generate and save together:
return DB::transaction(function () use ($data) {
$invoice = Invoice::create($data); // number assigned in creating()
return $invoice;
});
Use the new starting value for legacy cut-overs, not for padding. When migrating from an old system whose last invoice was INV-10432, set the invoice sequence's starting value to 10433 (or seed the counter row) before the first new invoice is issued. Do this as an explicit deployment step — a seeder or a one-off artisan command — so the value is versioned and reproducible across environments. Do not rely on it being set ad hoc in tinker on production.
Isolate the package behind one class. Even for a small package, wrap it:
final class NumberSequence
{
public function next(string $type): string
{
return AutoNumber::generate($type);
}
}
Six months from now, when the package is abandoned or you need per-tenant formats it cannot express, you change one file instead of grepping the codebase.
Per-type config, one key per document. Define invoice, quotation, receipt, purchase_order as separate sequences with their own prefixes. Do not reuse a single counter for different document types; you will not be able to unpick it later.
Multi-tenant: put the tenant in the key. If tenants must each have their own INV-000001, the sequence key has to include the tenant id (e.g. invoice:{$tenant->id}). Check whether the package supports arbitrary keys or only keys defined in config — if the latter, you need a config entry per tenant, which does not scale, and the wrapper class above is where you handle it.
Testing. In tests, reset the counter between cases (a RefreshDatabase migration truncation or an explicit reset in setUp) or accept that numbers differ per run and assert only on the shape (regex) rather than the exact value. Asserting INV-000001 exactly will make your suite order-dependent and flaky the moment you add a test.
unique index on the number column as the backstop, and be prepared to catch the resulting QueryException and retry once. If the package's counter store is a DB table, verify it uses lockForUpdate() (or an atomic increment) inside the transaction; if it does not, wrap the generation in DB::transaction(...) yourself and test with parallel requests before go-live.php artisan config:cache as part of deploy — a stale cached config silently produces the old format for as long as the cache lives. This is the single most common "the package ignores my config" cause.cache:clear, a Redis eviction, or a failover to a fresh node will restart the sequence and produce duplicates. Prefer the database-backed store for anything that must be unique forever, and if you must use cache, use a persistent store with eviction disabled.php artisan config:show for the actual merged config, (2) the counter table/row for that key, (3) whether the record was created inside a rolled-back transaction, (4) your model's creating hook firing twice (mass-assignment plus explicit assignment). DB::listen() in a local environment will show you the counter UPDATE and its value at the moment of generation.~1.0.1 constraint, commit the lock file, and read the diff before bumping. The Laravel 10 change in v1.0.1 is evidence that minor bumps here can carry framework-constraint changes.How can I help you explore Laravel packages today?