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

Eloquent Public Id Laravel Package

yieldstudio/eloquent-public-id

A Laravel package that adds unique, Stripe-style public IDs to Eloquent models, helping hide auto-incrementing primary keys and create cleaner, safer URLs. Includes configurable prefixes and simple trait-based integration.

View on GitHub
Deep Wiki
Context7

Getting Started

Install the package and pull in the trait:

composer require yieldstudio/eloquent-public-id

The package centers on a single trait you add to any Eloquent model whose records you want to expose externally:

use YieldStudio\EloquentPublicId\HasPublicId;

class Invoice extends Model
{
    use HasPublicId;
}

You still need a column to store the value. Add it in a migration (the package ships a default config, but the column is typically public_id):

Schema::table('invoices', function (Blueprint $table) {
    $table->string('public_id')->nullable()->unique()->after('id');
});

Where to look first: the package is small — read the trait source under vendor/yieldstudio/eloquent-public-id/src before trusting it in production. It's the fastest way to confirm the column name, generation strategy, and whether uniqueness is enforced or assumed. Check whether a config file is publishable (php artisan vendor:publish --tag=...) so you can override the column name and ID format.

First use case: replacing auto-increment IDs in URLs and API payloads. Instead of /invoices/1042 you expose /invoices/inv_8f3k2p9q, keeping sequential primary keys internal. That's the whole point of the package — it's a thin convenience layer, not an identity system.

A typical first pass:

// routes/web.php
Route::get('/invoices/{invoice:public_id}', InvoiceController::class);

Or override route binding on the model:

public function getRouteKeyName(): string
{
    return 'public_id';
}

Implementation Patterns

Route model binding by public ID. For a resource that should never leak the integer ID, set getRouteKeyName() to public_id. If you keep both bindings (some routes internal, some public), use explicit binding syntax {invoice:public_id} rather than changing the model globally — it keeps admin routes fast on the primary key while public routes use the opaque ID.

Custom column and format. If your schema already uses uuid, ulid, or a namespaced column, align the config/trait property with it instead of adding a redundant column. The package is opinionated but thin; if the generated format doesn't match your needs (e.g. you want ULIDs for sortability), either configure it or override the generation hook on the model rather than patching the vendor trait.

Factories and seeders. Factories will not know about public_id unless you add it. Either let the creating hook fire during create() (works when the trait generates on the creating event) or set it explicitly in the factory so tests are deterministic:

public function definition(): array
{
    return [
        'public_id' => Str::random(12),
        // ...
    ];
}

Deterministic IDs in factories make assertions and snapshot tests stable.

API resources. Treat the public ID as the only externally visible identifier. Return it explicitly and stop serializing id:

return [
    'id'         => $this->public_id,
    'created_at' => $this->created_at,
];

This boundary is the real value: once id is never exposed, you're free to change the public ID format later.

Backfilling existing tables. The trait only fires on model creation. For an existing table, write a one-off command/ migration that generates IDs for all rows, then make the column NOT NULL and unique. Don't rely on the trait to "fix up" legacy rows — it won't.

Integrating with soft-deletes and imports. Bulk operations (Model::insert(), raw SQL) bypass Eloquent events, so the public ID is never generated. Route any code that must produce IDs through create()/save(), or generate the value yourself before a bulk insert.

Exposing lookups. Build a scoped query or findByPublicId helper so controllers never hardcode the column:

Invoice::where('public_id', $publicId)->firstOrFail();

Centralizing it means a future format change is one place.

Gotchas and Tips

Uniqueness is not free. Random string generation has collision probability. Always put a unique() index on public_id and add a retry loop (or catch the duplicate-key exception) around creation. Never assume "it's long enough" — a unique constraint turns a rare bug into a recoverable error instead of a silent duplicate.

Nullability on the migration. If you add the column as nullable() and forget to backfill, existing records get NULL public IDs and route binding silently 404s. New rows populate, old ones don't. Plan the backfill and the NOT NULL transition together.

getRouteKeyName() is global. Overriding it changes binding for every route on that model, including admin ones. Prefer explicit {model:public_id} bindings where you want them to differ. This trips people up when an admin link suddenly uses the public ID and a lookup against the integer column fails.

Mass-assignment and the trait's hook. The generated ID is usually set on the creating event, so it's not affected by $fillable. But if you expose the column in forms or factories, add it to $guarded or $fillable deliberately — accidentally letting clients set public_id is a trivial enumeration/spoofing vector.

Adoption is a real risk. This package has essentially no dependents and a single maintainer. The score is low for that reason, not code quality. The trait is small enough to copy into your own project as a HasPublicId concern in an afternoon — do that if the dependency becomes load-bearing. Pin the version and watch the changelog.

Debugging generation. If IDs are missing, check: (1) the trait is actually on the model, (2) the column exists and is spelled correctly, (3) the record was created through Eloquent, not insert(), and (4) no withoutEvents() wrapper suppressed the creating hook. Those four cover almost every "the ID is null" report.

Extension points over edits. Prefer overriding the generation method or configuring the column name over editing vendor files. Because the package is tiny, the temptation to patch it is high — a config/property override survives composer update; a vendor edit does not.

Testing. Assert uniqueness and format in a unit test, and assert that id never appears in public responses. The second test is the one that catches regressions when someone adds a resource or changes a serializer.

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.
adachsoft/changelog-linter
adachsoft/collection
payerurl/telegrambot-crypto-payment-instant-payout
dskripchenko/laravel-hybrid-search
riorizkyrainey/lara-ced
mehedimi/static-vel
zharp/zharp
nonsapiens/bigquery-model-sync
lgu-bayambang-ict-office/lgu-laravel-development-standards
andydefer/laravel-support
se7enxweb/flysystem
lampwick/laravel
jeslage/statamic-emojis
micahdshackelford/rmap-laravel
draftsmaninc/draftsman
square-bit/laravel-volition
himito/laravel-mailman
htinlynn/genius-helper
maco-studios/hephaestus
adachsoft/php-code-style