luilliarcec/laravel-username-generator
Laravel Username Generator creates unique, customizable usernames for Laravel apps from names, emails, or custom strings. It handles collisions, supports configurable patterns and validation, and integrates easily with Eloquent models and registration flows.
composer require <vendor>/<package> and add the HasUsername trait to your authenticatable model (typically App\Models\User). The trait hooks into the Eloquent creating event via a bootHasUsername() method, so no service provider registration is normally required.config/username.php or similar) for length/prefix/column settings, and the migration that adds the username column. Confirm the column is NOT NULL-safe and has a unique index before you rely on it.username — the generator does not guarantee collision-free output under concurrency; the database constraint is your real safety net.creating event fire, and confirm a username is populated. Then wire up login: if you authenticate by username, override username() (Fortify) or adjust the credentials() array in your LoginRequest to ['username' => ...].Standard model setup
class User extends Authenticatable
{
use HasUsername;
}
Login by username (Fortify)
public function username(): string
{
return 'username';
}
Route model binding by username
public function getRouteKeyName(): string
{
return 'username';
}
Factories and seeders — since 6.0.1 the trait always regenerates on creating, a username passed to create() is discarded. For fixtures that need a deterministic handle, set it after the fact:
$user = User::factory()->create();
$user->update(['username' => 'fixture-user']);
For bulk fixtures where you need exact values, bypass events entirely:
DB::table('users')->insert([...]); // no creating event, no generation
Bulk imports — any path that uses insert()/upsert() skips model events, so usernames will be null. Follow the import with a backfill job that iterates models and calls save() (which fires creating only on new models — for existing rows call your generator directly or use update()).
Collision handling — wrap creates in a retry loop that catches Illuminate\Database\QueryException for duplicate-key errors, since the unique index can still reject a generated value.
Testing — assert on the generated column with assertDatabaseHas, and if the generator uses randomness, freeze time or assert a pattern (assertMatchesRegularExpression) rather than an exact string.
username surviving creation (imports, admin-created accounts, factory overrides) is now broken again. Treat 6.0.1 as the corrective release and pin it; do not stay on 6.0.0.username to create()/updateOrCreate() expecting it to stick. It is overwritten in the creating handler. Explicit values must be applied after the model exists (update(), forceFill()->save(), or saveQuietly()).saveQuietly() / withoutEvents() are the escape hatches when you must control the value during creation — but remember this also suppresses other model events (observers, audit logs).username in $fillable for user-facing mass assignment. It is generated, and exposing it invites overwrite attempts that silently do nothing.QueryException at an unexpected point.Alice and alice will collide in confusing ways.bootHasUsername() runs alongside your own booted()/boot() hooks. If you also define a creating listener, ordering is registration order — don't assume your listener sees the final generated value.User::creating and inspect $model->getDirty(); verify the trait is actually booted (class_uses_recursive) and that your username column exists and isn't silently truncated by length limits (a too-long generated value may be cut off by MySQL non-strict mode and then collide).creating listener registered before the trait's — or simply set the value after creation.How can I help you explore Laravel packages today?