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

guava/laravel-sequence

A Laravel package for generating sequential numbers for models, invoices, orders, and other records, with configurable prefixes, formatting, and database-backed, concurrency-safe increments through a simple API.

View on GitHub
Deep Wiki
Context7

Getting Started

guava/sequence (repo GuavaCZ/laravel-sequence) gives Eloquent models atomic, database-backed sequence numbers — invoice numbers, order references, ticket IDs — with pluggable strategies and output formatting. The counter state lives in a sequences table, not in memory, so it survives workers, deploys, and queue jobs.

Minimum steps

  1. Install: composer require guava/sequence. Version 1.2.0 adds Laravel 12 support; there are no breaking changes from 1.1.0, so it's a safe composer update on Laravel 10/11 too.

  2. Publish and migrate — the package ships a config file and a migration for the sequences table:

    php artisan vendor:publish --tag="sequence-config"
    php artisan vendor:publish --tag="sequence-migrations"
    php artisan migrate
    
  3. Where to look first, in this order:

    • The README's "Usage" section — the exact trait + sequence() shape is the whole API surface you'll use daily.
    • config/sequence.php — the strategy registry; this is where you plug in custom behaviour.
    • The published sequences migration — this table is the ground truth when debugging, and it's the first place to look when numbering looks wrong.
  4. First use case — a formatted invoice number on create:

    use Guava\Sequence\Concerns\HasSequence;
    use Guava\Sequence\Contracts\HasSequence as HasSequenceContract;
    use Guava\Sequence\Sequence;
    
    class Invoice extends Model implements HasSequenceContract
    {
        use HasSequence;
    
        public function sequence(): Sequence
        {
            return Sequence::make('invoices')
                ->initialValue(1)
                ->format('INV-{year}-{value}');
        }
    }
    

    After that, Invoice::create([...]) fills the number on save — you never call the generator manually in the happy path.

Treat the exact builder method names as version-specific: confirm them against the README shipped in vendor/guava/sequence before standardising on them across a codebase.

Implementation Patterns

Model trait + a sequence() definition per model. This is the intended integration point. Keep the definition small and declarative; anything dynamic (dates, tenant ids, parent model data) belongs in the format placeholders or a callback, not in a service you call by hand.

Strategy selection is a design decision, not a detail.

  • Incrementing — human-friendly, ordered, but leaks volume (customer #37 knows you have 37 customers). Use for invoices/orders where ordering matters to support staff.
  • Random — good for public-facing references where guessability is a concern.
  • ULID/UUID — when you want sortable-unique or globally unique and don't care about readability.
  • Callback — when the value depends on model state at save time.

Period-scoped counters via the sequence name. For "resets every year/month", the cleanest pattern is to make the period part of the sequence identity or format and let each period's counter be its own logical sequence, rather than mutating initialValue at runtime:

Sequence::make('invoices-'.now()->format('Y'))
    ->initialValue(1)
    ->format('INV-{year}-{value}');

This gives you INV-2025-0001, INV-2026-0001 and no midnight-reset job to babysit.

Tenant scoping. Same idea — include the tenant key in the sequence name (invoices-tenant-42). Every tenant gets independent numbering, and there's no cross-tenant lock contention on a single hot row.

Backfilling an existing table. Don't start at 1 on a table that already has rows. Compute MAX(number)+1 (or your highest existing formatted value) and pass it as initialValue in a one-off migration/command, then verify with SELECT * FROM sequences. Do this before the first new record is created, not after.

Custom strategies for company-specific formats. Implement the package's strategy contract, then register it in config/sequence.php. This keeps sequence() definitions clean and makes the format testable in isolation — a strategy is just a class, so unit-test it directly rather than through a model.

Tests and factories. The sequences table must exist in your test database. With RefreshDatabase that's automatic once the migration is published. Assert on the format contract (assertMatchesRegularExpression) rather than hard-coded numbers, so tests don't break when you change initialValue. If you create models via factories inside DB::transaction() in tests, expect gaps.

Queues, imports, seeders. Any path that goes through create()/save() gets a number for free. Wrap large imports in chunks and let each chunk create models normally — resist the urge to bulk-insert.

Gotchas and Tips

Gaps are by design, not a bug. The counter advances in the database; if the surrounding transaction rolls back or a job fails after the number is consumed, that number is gone. If you have a legal/accounting requirement for truly gapless numbering, this package (like nearly every DB-counter approach) is the wrong tool — you need a single-writer, post-commit allocation step. Decide this before you build on it.

initialValue only applies when the sequence row is created. Changing it in code later does nothing. To re-baseline a live sequence you must update or delete the row in sequences — treat that table as production data and lock down manual edits.

Bulk inserts silently skip numbering. Model::insert(), insertOrIgnore(), raw query-builder inserts, and withoutEvents() all bypass the Eloquent hooks the trait relies on. Symptom: rows with NULL numbers that only show up weeks later in exports. If you must bulk-insert, generate values explicitly in the same pass.

Transactions and lock ordering. Sequence allocation takes a lock on its row. Two jobs that allocate multiple different sequences in one transaction in different orders can deadlock. Standardise the order in which you create models inside a transaction, and keep transactions that allocate sequences short.

Formatted strings sort lexicographically. INV-2025-999 sorts after INV-2025-1000. Always keep the raw integer in its own column and sort/paginate on that; use the formatted value for display and search only.

Config caching. Custom strategies registered in config/sequence.php won't be picked up until php artisan config:cache is re-run. If a strategy class "isn't found" in production but works locally, this is almost always why. Same applies if your strategy has constructor dependencies — make sure the container can resolve it, and avoid resolving services at config-build time.

Don't use the sequence as a security token. Even random strategies are references, not authorisation. Keep route-model-binding scoped to the authenticated user/tenant and don't let a guessable invoice number become an access path.

Debugging checklist when a number looks wrong, in order: (1) SELECT * FROM sequences WHERE name = '...' — is the counter where you expect? (2) Did the row get created through Eloquent or a bulk path? (3) Was the surrounding transaction rolled back? (4) Is the config cached with an old strategy? (5) Is format() producing what you assumed for the current period?

Upgrading to 1.2.0 / Laravel 12. Purely additive framework support plus a code-style pass — no deprecations, no config or API changes, safe minor bump. Re-publish and diff the config file only if you want to pick up new keys (vendor:publish --force overwrites, so diff first). The real upgrade risk on Laravel 12 is elsewhere: check that any other packages pinned against your models/illuminate/support also allow 12 before bumping the framework constraint.

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.
frankyso/ipaymu
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment