nurmuhammet/dynamic-fields
A Laravel package for adding dynamic, customizable fields to your Eloquent models. Define field types and configurations, then let users create and manage their own custom fields and values without schema changes.
Before writing a line of code, confirm the package is even loadable — this is a 2-star repo with zero dependents, so the README is likely thinner than the code.
Check compatibility first. Open composer.json in the repo and read the require block. You need to know:
laravel/nova: ^4.0 vs ^5.0). Nova 4/5 field APIs differ enough that a wrong guess wastes an afternoon.laravel/nova is listed as a require (bad, causes conflicts) or require-dev/suggest (normal).Install with an exact pin so a silent upstream push can't break your admin:
composer require nurmuhammet/dynamic-fields:"1.0.*"
Publish/compile assets. Nova 3 needed php artisan nova:publish; Nova 4/5 uses nova.mix and auto-registers a field's dist/js via its service provider. Look in src/ for a *ServiceProvider calling Nova::serving() or Nova::script(), and check whether a dist/ folder is committed. If dist/js is missing you'll have to build it yourself from resources/js.
Where to read in the source, in order:
src/*.php — the field class extending Laravel\Nova\Fields\Field. This tells you the real method names (dependsOn, hideWhen, resolveUsing, whatever it actually ships).component property / ->component('...') call — the Vue component name.resources/js/components/*.vue — how it actually reacts to sibling values. This is the only reliable documentation.README.md last, as a sanity check against the source.First use case — the canonical one. Gate a field on another field's value: show Discount % only when Has discount is checked.
use Nurmuhammet\DynamicFields\DynamicField; // verify namespace in src/
Boolean::make('Has discount'),
DynamicField::make('Discount %')
->dependsOn('has_discount') // confirm method name in src/
->showWhen('has_discount', true)
->rules('numeric', 'min:0', 'max:100'),
Smoke test in a throwaway Resource before touching a real one. Create the field, open the form, toggle the dependency, watch the network response and Vue devtools. If the field doesn't react, the problem is almost always assets not published or a mismatched component name — not your code.
Treat it as a visibility/reactivity layer, not a validation layer. The package decides what the user sees; Laravel decides what is stored. Keep those concerns separate:
DynamicField::make('Shipping notes')
->dependsOn('delivery_method')
->showWhen('delivery_method', 'courier')
->rules('required_if:delivery_method,courier') // server-side truth
->fillUsing(function ($request, $model, $attribute) {
$model->{$attribute} = $request->input('delivery_method') === 'courier'
? $request->input($attribute)
: null; // clear stale values
}),
Compose dependencies explicitly. Conditional fields that depend on two inputs are where most bugs live — declare both and make the frontend logic deterministic:
->dependsOn(['status', 'type'])
->showWhen(fn ($values) => ...) // only if the package supports closures;
// otherwise chain multiple ->showWhen() calls
Keep reactive fields in the same panel and same form context. A DynamicField in panel A cannot see a field in panel B with any reliability, and never see fields that are onlyOnDetail() or onlyOnIndex(). Group dependent fields into one Panel::make(...) or one Row.
Extend rather than fork, where you can. Subclass the field for house conventions:
namespace App\Nova\Fields;
class ConditionalField extends \Nurmuhammet\DynamicFields\DynamicField
{
public function component()
{
return 'app-conditional-field'; // your own Vue component
}
}
Then override the Vue side in resources/js/fields/ and register it in nova.mix / nova-components — this is the escape hatch when the bundled component is missing a feature (multi-field deps, transitions, dependent dropdowns).
Wrap repetitive config in a Resource trait or a static factory so a dozen resources don't drift:
trait HasConditionalDiscount
{
protected function discountField(): DynamicField
{
return ConditionalField::make('Discount %')
->dependsOn('has_discount')
->showWhen('has_discount', true)
->rules('nullable', 'numeric', 'between:0,100');
}
}
Reactive dropdown pairs (country → city) are the highest-value pattern and the one most likely to need custom Vue: use ->dependsOn('country')->options(...) if supported, otherwise use a resolveUsing that returns an empty options array and let a small custom component populate it from field.meta.
Test the behaviour, not the field. Nova's own test helpers plus Dusk for the reactivity:
$response = $this->actingAs($admin)
->getJson("/nova-api/{$resource}/{$id}")
->assertOk();
$this->assertArrayHasKey('has_discount', $response->json('resource.fields.0'));
For Dusk: ->visit('/nova/resources/users/1/edit')->check('@has-discount')->assertSee('Discount %').
Pin and vendor. Given the maintenance risk, commit a lockfile-stable version and consider copying the field class into app/Nova/Fields once you know which 40 lines you actually use. That converts an unmaintained dependency into ~1 file you own.
The repo is effectively unpinned from your future. 2 stars, 0 dependents, no release cadence. Assume no security fixes. Wrap usage behind your own subclasses so swapping it out later is a one-file change, and add a composer.json conflict/exact-version constraint rather than ^1.0.
Stale compiled assets are the #1 cause of "it doesn't react." Run php artisan nova:publish (or npm run dev in the package), then php artisan view:clear && php artisan optimize:clear. Hard-reload the browser — Nova caches hashed JS filenames.
Hidden ≠ absent. A field hidden by the package is still in the DOM payload, still submitted, and still validated. If a user previously saved a value and you now hide the field, the old value persists. Use fillUsing or a model mutator to null it out — the frontend will never do this for you.
Closures don't cross the JSON boundary. Whatever you pass to ->withMeta() must be serialisable. Compute server-side values in resolve() / resolveUsing() and hand the component plain scalars, never callbacks.
dependsOn keys are the field attribute, not the label or the DB column alias. Boolean::make('Has discount') resolves to has_discount. Getting this wrong fails silently — the field just never shows.
Index and detail views have no sibling values to depend on. A field conditioned on a form input will often render empty or throw on the index. Guard with ->hideFromIndex() / ->hideFromDetail() unless you've verified the vendored component handles the missing-state case.
->rules() on hidden fields still fires. Use nullable/sometimes plus required_if, and remember Nova's rules run through Laravel validation — a required_if against a field the user can't see is a support ticket waiting to happen.
Debug from the serialized payload, not the UI. GET /nova-api/{resource}/{id} returns the field's full JSON including meta and the component name. Nine times out of ten the issue is there: wrong component, empty meta, or a dependency key that doesn't match.
Vue devtools is your source of truth. Inspect the component's props.field and watch it on dependency change. If watch never fires, the sibling field's dependsOn registration is missing — the parent form has to know to broadcast, and some packages require the dependent field to be listed, not the dependency.
Extension points, in order of safety: (1) ->withMeta() for initial state, (2) ->resolveUsing() / fillUsing() for server-side behaviour, (3) subclass + custom component(), (4) fork the Vue file. Stop at level 3 unless the package is dead — forking puts you on the hook for every Nova upgrade.
Nova upgrades break fields quietly. After bumping Nova major, re-run the smoke test on every reactive form. A field that renders but doesn't react is worse than one that errors, because QA will miss it.
How can I help you explore Laravel packages today?