gldrenthe89/nova-string-generator-field
A Laravel Nova field that generates random strings directly in resource forms. Useful for creating unique codes, tokens, and identifiers, with configurable length and character sets. Easy to add to any Nova resource and customize for your workflow.
Requirements. This is a Laravel Nova field, so Nova must already be installed and licensed (laravel/nova lives in a private Composer repo — make sure your auth.json/Composer credentials work before you start). The 4.x line targets Nova 4/5-era Laravel (11/12, PHP 8.2+). Pin ^4.1 if you're on Laravel 12; the release notes only guarantee "laravel 12 compatibility" from that tag.
composer require gldrenthe89/nova-string-generator-field:^4.1
No publish step is normally needed — Nova field packages register their compiled JS through Nova::script() in the package's service provider, which Laravel auto-discovers. If the button doesn't render, that registration is the first thing to check (see Gotchas).
Where to look first. The package is tiny; read it in this order:
vendor/gldrenthe89/nova-string-generator-field/src/NovaStringGeneratorField.php — this is your entire API surface. Every fluent method you'll use day-to-day (length, characters, prefix/suffix, etc.) is a property setter that ends up in the field's meta. Read it once, top to bottom; it takes five minutes.resources/js/field.js (or components/) — the Vue component holding the Generate button and the actual random-string loop. This tells you how the string is produced, which matters for security decisions (see Gotchas).composer.json — confirm the laravel/nova constraint matches your Nova major. Compatibility gaps here are the single most common cause of "field renders as a blank text input".First use case. Swap an existing Text field for the generator on a unique-identifier column, and verify it in the browser:
use Gldrenthe89\NovaStringGeneratorField\NovaStringGeneratorField;
public function fields(NovaRequest $request): array
{
return [
NovaStringGeneratorField::make('API Key', 'api_key')
->length(32)
->characters('abcdefghijklmnopqrstuvwxyz0123456789')
->rules('required', 'max:32')
->hideFromIndex()
->help('Click Generate, or type your own key.'),
];
}
Back it with a migration column wide enough for the configured length ($table->string('api_key', 32)->unique();), add api_key to $fillable, then create a resource through the Nova UI and confirm the button fills the input and the value persists on save. That single round-trip validates installation, field registration, and your DB column width all at once.
Treat it as a Text field with a helper button, nothing more. Because it serialises the same way as Text, you can swap it in without touching index/detail columns, filters, or fillable. If you already have Text::make('Reference', 'reference'), the migration is a one-line import change plus the fluent config. Keep the underlying attribute a plain string column.
Encode your alphabet, don't hand-type it. The most common real-world usage is a handful of presets:
->characters('0123456789') // numeric reference code
->characters('ABCDEFGHJKLMNPQRSTUVWXYZ23456789') // human-readable, no I/O/0/1
->characters('0123456789abcdef') // hex-ish
->length(8) // match the column width
Drop visually ambiguous characters (0/O, 1/I/l) for anything a human will read aloud or retype from a printed label.
Always add a server-side fallback. The Generate button is client-side only. Anything that creates records outside the Nova form — factories, seeders, imports, Model::create(), another Nova resource — will save null. Cover that in the model:
protected static function booted(): void
{
static::creating(function (self $model) {
$model->api_key ??= Str::random(32);
});
}
Keep the two generators aligned in length and alphabet, or you'll ship values that fail your own max:32 rule the day a seeder runs.
Scope visibility to the create form. Generated identifiers are usually write-once. Restrict the button (and optionally the whole field) to creation so nobody rotates a live key by accident:
NovaStringGeneratorField::make('API Key', 'api_key')
->length(32)
->onlyOnForms()
->canSee(fn (NovaRequest $request) => $request->resourceId === null)
->rules('required', 'max:32')
->creationRules('unique:tenants,api_key')
->updateRules('unique:tenants,api_key,{{resourceId}}'),
creationRules matters more than usual here: the value is user-editable, so someone can paste a duplicate and you want a validation error rather than a 500 from the unique index.
Hide it from index and detail where it's sensitive. ->hideFromIndex() for anything token-like. Note that hiding is cosmetic — the value is still returned by the API and still visible in the DB. If it's a genuine secret, don't store it plaintext (see Gotchas).
Pair with a hashed cast for password-style use. If you're using the password mode to mint credentials:
// Model
protected function casts(): array
{
return ['api_secret' => 'hashed'];
}
The user generates, sees, and copies the plaintext once; the cast hashes on save. Then show only a masked/— placeholder on detail and don't try to re-display it. Be explicit in ->help() that the value cannot be retrieved afterwards, because the field itself will happily re-render the hash on the edit form.
Centralise the presets in a base resource or trait. If five resources need a 32-char key with the same alphabet, put a apiKeyField() helper on an abstract Resource class. When the alphabet or length changes, you change one method instead of grepping resources — and your factory/observer fallback stays in sync with it.
Test at the boundaries, not the field. Nova fields are thin; asserting the Vue button works is low value. Instead assert (a) the field serialises with the meta you expect, and (b) records created without Nova still get a value:
it('generates a key for non-Nova creation', function () {
$tenant = Tenant::create(['name' => 'Acme']);
expect($tenant->api_key)->toHaveLength(32);
});
it('exposes the generator config to the frontend', function () {
$field = NovaStringGeneratorField::make('API Key', 'api_key')->length(32);
expect($field->meta())->toMatchArray(['length' => 32]);
});
Then hit the Nova create endpoint in a feature test if you have the harness set up; otherwise a manual smoke test per release is fine for a package this size.
The generation happens in the browser, not on the server. Nothing is written until the form is submitted, and nothing is generated at all if the user leaves the field blank. This is the number one surprise: teams assume the field guarantees a value. It doesn't — you need required validation plus the model-level fallback described above.
Check the RNG before you trust it with secrets. The package is a convenience field; the generation loop is almost certainly Math.random()-based, which is not cryptographically secure. For API keys, session tokens, password resets, or anything an attacker benefits from guessing, generate server-side with Str::random() / random_bytes() in an observer or a dedicated action, and use this field only as a UI convenience for non-security identifiers (reference codes, slugs, filenames). Read the JS once and make the call deliberately.
Column width must be ≥ configured length. ->length(64) against string('token', 32) gives you a truncated value on MySQL (silent, in non-strict mode) or a hard error in strict mode. Mismatches also show up as validation failures that look like the field is "not working". Grep your migrations whenever you change a length.
characters() and length interact in ways you should verify. If the package supports a prefix/suffix, confirm from the source whether it counts toward ->length() or is appended. Getting this wrong by one character is the classic cause of a max:32 rule rejecting a "32-char" generated value.
Numeric alphabets still produce strings. ->characters('0123456789') with ->length(8) can produce 01234567. If you add a numeric rule, PHP will parse it as an int and lose leading zeros; if you cast the attribute to integer, the same thing happens. Keep numeric-looking codes as string columns and validate with digits:8, not numeric.
Don't mix it with Nova's Password field. Nova's Password field intentionally never repopulates a stored value on edit. If you need "generate → hash → never show again", use this field with a hashed cast and hide it on detail, rather than layering two different password behaviours. Also be aware that the field is editable by design — a user can overwrite a generated key with test123, and only your validation rules will stop them. Add ->rules('min:32') or a regex if the alphabet matters.
Asset registration is the usual "it's broken" cause. If the field renders as a bare text input with no button: check that the package's service provider appears in php artisan about / bootstrap/cache/packages.php, confirm the compiled JS is being injected (Network tab on the Nova page — look for the field's field.js), and clear php artisan nova:publish, php artisan view:clear, and your browser cache in that order. Nova caches its published assets aggressively and a stale bundle from a previous version is a very common false alarm after composer update.
Nova major-version drift. Nova ships breaking frontend changes between majors (Vue 2 → Vue 3 in Nova 4, and again in Nova 5's toolchain). A field package that hasn't declared support for your Nova major may install fine and then fail at render time with a console error in the Vue component. Pin the field in composer.json and, before any Nova upgrade, check the package's composer.json constraint and its issue tracker. This package is a single-maintainer, five-star project — assume you'll be doing your own compatibility verification each cycle, and budget for it.
Upgrades can overwrite nothing, which is the good news. There's no config to publish and no migrations in this package, so composer update is genuinely low-risk. The risk sits entirely in the JS bundle being replaced — retest the button after every bump.
Debugging tips. The fastest loop is Vue DevTools on the Nova form: inspect the field component's props and you'll see exactly which meta values arrived from PHP, which immediately tells you whether the problem is server-side (fluent method not serialising) or client-side (button handler erroring). For anything odder, Log::debug($field->meta()) in the resource's fields() confirms what PHP thinks it's sending.
Extension points. There aren't many, and that's fine for a field this small. Configure via the fluent methods, and put any real behaviour (defaults, hashing, uniqueness, rotation policy) in your model, observer, or a Nova action. Avoid subclassing NovaStringGeneratorField — the internals are minimal and you'd own the fork for no gain. If you need rotation with an audit trail, that's a custom Nova action calling Str::random(), not a variant of this field.
How can I help you explore Laravel packages today?