ajmariduena/dynamic-field
Dynamic Field for Laravel lets you attach custom fields to Eloquent models without writing new migrations. Configure field types, validation, and options, store values as JSON, and access dynamic data through a simple, fluent API—ideal for user-defined forms and flexible schemas.
Install and confirm discovery. Require it like any Nova field, then check that Nova picks up the service provider automatically:
composer require ajmariduena/dynamic-field
php artisan nova:field ajmariduena/dynamic-field # only if you intend to vendor the assets
Nova 4/5 auto-discovers providers via extra.laravel.providers in the package's composer.json. If the field does not appear in the browser, run php artisan nova:publish and hard-refresh — a missing compiled js/field.js is the usual culprit.
Read three files before anything else.
src/DynamicField.php — the PHP field class. This is where the public API lives (make(), whatever setter methods it exposes, and any resolve/fill callbacks).resources/js/field.js — the registered Vue component name. Note the exact string, because ->component('...') must match it.resources/js/components/FormField.vue — tells you which other fields the component subscribes to and what shape of data it expects. This is the whole contract of a "dynamic" field.First use case: one field driving another. The canonical scenario for a field like this is a select/text input whose options, visibility, or even input type change based on a sibling field's value. Start with a two-field resource:
use Ajmariduena\DynamicField\DynamicField; // verify namespace against src/
public function fields(NovaRequest $request)
{
return [
Select::make('Type')->options([
'text' => 'Free text',
'select' => 'Picklist',
]),
DynamicField::make('Value')
->dependsOn('type') // confirm the actual method name in src/
->options(fn () => /* ... */),
];
}
Get that single field reacting to a value change before you try anything clever with closures, validation, or index-view rendering.
Closure-backed configuration. Dynamic fields exist so that configuration is evaluated per-request rather than at field-definition time. Put anything request-dependent (current user, $request->resourceId, related model state) inside a closure passed to options(), dependsOn(), or a resolveUsing()-style hook. Anything that is static should stay a plain array — closures are re-evaluated on every serialization and are the first thing to profile when a resource form gets slow.
Dependent selects. The most common production pattern is a cascading pair (country → city, project → task). Keep the dependency graph one level deep on the field itself and do the filtering server-side:
DynamicField::make('City')
->dependsOn('country')
->options(fn (NovaRequest $request) => City::where('country_id', $request->country)->pluck('name', 'id'));
Reach for Nova's own dependsOn() / DependsOn container before duplicating that behaviour inside the dynamic field — the package is worth using when the input type changes, not just the options.
Type-switching inputs. If the field renders a different control based on a discriminator (text vs. number vs. select), mirror that logic on the PHP side in rules(). Nova's validation runs against whatever the field declares, so a field that can be either a string or an integer needs conditional rules:
->rules(fn (NovaRequest $request) => $request->type === 'number'
? ['required', 'numeric']
: ['required', 'string', 'max:255'])
Reusable presets. Rather than copy-pasting DynamicField::make()->dependsOn()->options() across resources, extract a small helper (a static factory or a custom Nova field wrapping this one). This package is low-level; most teams end up writing a thin domain-specific wrapper like StatusField or CascadingSelect and hiding the raw DynamicField behind it.
Where it fits relative to other Nova features. If what you need is a field that only shows or hides, use ->hideWhen() / ->showWhen() — no extra package needed. If you need a display-only computed value, Text::make(...)->resolveUsing() already covers it. Use DynamicField when the component itself has to change, or when option sets are genuinely request-dependent and large enough that you don't want them inlined in the field definition.
src/DynamicField.php end-to-end; the public method names (dependsOn, options, resolveUsing, whatever they actually are) are the real documentation.FormField, HandlesValidationErrors) and Vue 3 migration. A package this small may have been built against one and not tested against the other. Check composer.json for the laravel/nova constraint and package.json for Vue 2 vs 3 before you build a workflow on top of it.watch the dependency field and re-render. If the field renders correctly on page load but does not update when the sibling changes, the bug is in the component's watcher, not in your fields() method.indexQuery serialization the same way they are on forms. Expect to add ->onlyOnForms(), or an explicit ->resolveUsing() for the index representation, otherwise you'll see raw IDs in the resource table.php artisan nova:publish and hard-refresh. Stale compiled assets produce the maddening "component not found" error that looks like a PHP problem but isn't.browser console → Vue devtools) 2) Is the field serialized with the meta you expect? (Nova.request() in the console, or dump in resolve) 3) Is the watcher firing? Add a console.log in watch.->component() can be swapped for your own, and resolveUsing/fillUsing cover most behavioural variance. Forking the package for a one-line change is rarely worth it — vendor it under your own namespace if you must patch it, since upstream maintenance is uncertain.How can I help you explore Laravel packages today?