dillingham/nova-ajax-select
A Laravel Nova field that swaps the default select for an AJAX-powered searchable dropdown, so users can quickly find records in large related resource sets as they type. Works with belongsTo, belongsToMany, and morphTo relationships, with support for custom search columns and query constraints.
Architecture fit. dillingham/nova-ajax-select is a Laravel Nova custom field — a presentation-layer control, not a domain or platform component. It swaps an eager-loaded Select/BelongsTo for a search-as-you-type input that resolves options over an async endpoint (typically Nova's resource search route). It carries no schema, persistence, or business logic, which is the right shape for an admin-UI concern but also means it is hard-bound to Nova: it does not port to Filament, Backpack, or a bespoke admin panel. A TPM should classify it as a narrow internal-tooling accelerator, justified only where a Nova form must reference high-cardinality or fast-changing option data (customers, tenants, SKUs, CRM-synced accounts). Outside that boundary it has no strategic value.
Integration feasibility. The mechanical integration is genuinely small: Composer install, Nova asset auto-discovery, one field definition on one Resource, and fluent configuration of the resource, label column, and search behavior. A competent Nova developer can produce a working proof-of-concept in well under a day, and the package replaces roughly one to two developer-days of bespoke custom-field work at zero vendor cost. The actual gate is not effort but host compatibility. The last release is v1.0.4 (August 2020), which predates Nova 4's Vue 3 rebuild. Treat Nova 3.x as the supported path and Nova 4/5 as a spike that must be validated — a failed mount there can be silent, so the smoke test must explicitly assert the field renders, not merely that the form loads. The release also fixes a parent() resolution bug (#21); pin 1.0.4 rather than floating the constraint. "Fixed" may mean graceful degradation rather than correct behavior, so a field rendered inside a modal or nested field group needs deliberate testing.
Technical risk. Three risks dominate, in order:
searchable/relatable query or the package's query hook, verified against src/, not inferred from a README.LIKE '%term%' on an unindexed column is trivial on a seeded table and a page-lock incident on production volume. Front-end debounce is UX, not a control: server-side minimum query length, a hard limit(20–50), orderBy on an indexed column, and a rateLimiter on the route are all required.Key questions. (a) Are we on Nova, and which major version — and if 4/5, has a mount spike actually passed? (b) Does the target field's option source genuinely exceed native searchable-select capability, or is this dependency buying parity we already have? (c) Who owns the fork, and where does the internal tagged version live? (d) Has the search query been audited end-to-end for tenant scoping, including the no-match and clear-and-resubmit paths? (e) Are the searched columns indexed at production shape? (f) What is the fallback when the endpoint errors and the admin still needs to submit the form?
Stack fit. The package assumes the standard Laravel + Nova stack with Composer and Nova's asset pipeline. Confirm the intersection of composer.json constraints (PHP/Laravel/Nova) with the installed runtime before writing any integration code — that single file answers whether the effort is viable. There is no meaningful fit outside Nova, and no partial-adoption story: it is a Nova field on a Nova Resource, or it is nothing.
Migration path. Adopt per-Resource, not per-application. Pick a single low-traffic Resource whose relationship points at a table you would never render as <option> tags. Swap the existing Select/BelongsTo for AjaxSelect::make(...)->resource('customers')->label('name'), keeping the original field definition in the form behind a config/env flag. That gives a reversible switch: rollback is a flag flip, not a revert-and-deploy. The migration is a field-level replacement, not a data migration — saved values are foreign keys either way, so the blast radius is bounded to form rendering and the search endpoint.
Compatibility. This is a gate, not a checkbox. Pin dillingham/nova-ajax-select:1.0.4 in composer.lock and record it in the dependency register. On Nova 3.x the integration is likely straightforward; on Nova 4/5 the Vue asset may not mount and may fail without a console error, so a spike with an explicit mount assertion must precede any commitment. If we are upgrading to Nova 4 anyway, benchmark the native searchable select on the same Resource and capture page-weight and time-to-interactive numbers — if native hits them, drop the dependency rather than carry it. Also confirm the exact fluent method names and any query hook against src/; the source is the documentation.
Sequencing. Five steps, each a decision point:
composer show laravel/nova, note the major version. Stop here if not on Nova.parent() path.Maintenance. Assume dormant upstream. Budget an internal owner and a fork contingency from the start: a VCS repository entry with a tagged internal version carrying 1.0.4 as the baseline. The surface area is small enough that a Laravel developer can read the whole field class and Vue component in an afternoon, which is what makes the fork credible. The recurring cost is not patching but watching — the trigger for action is a Nova major upgrade, and the fork should be stood up the moment one is on the roadmap rather than after it breaks production.
Support. There is no vendor, no doc site, and self-support issues. Designate one teammate as SME and write the field's configuration — resource name, label column, scoping model, endpoint route — into the runbook so the next engineer is not reverse-engineering src/. Stakeholder expectation should be set explicitly: this is adopt-and-own, not buy-and-forget, and it carries no security-response commitment. That framing is acceptable for internal admin tooling and unacceptable for anything customer-facing.
Scaling. The scaling axis is query cost, not user count. Debounce is not rate limiting — a stale browser tab or a determined caller can hammer the endpoint — so the route needs a rateLimiter as cheap insurance against a self-inflicted DoS. Enforce a minimum query length, a hard limit, and orderBy on an indexed column at the server boundary; the component makes it look fast, the server makes it stay fast. Test against a production-shaped table before rollout, and prefer prefix matching or full-text search where the dataset warrants it. Instrument error rate and latency around the search route from day one — without it, the failure mode that matters is invisible.
Failure modes. Enumerate them and design against each:
Ramp-up. Low. A Laravel/Nova developer reaches productive use in a day: install, pin, register, read src/, done. The learning curve that actually matters is institutional, not technical — teaching the team that this dependency is internally owned, that the endpoint is a security surface, and that the fallback behavior is part of the integration. Plan the pilot at one to two weeks of real admin traffic behind the flag, then a go/no-go against the captured metrics: mount success, endpoint error and latency rates, admin form completion, and a comparison against native searchable selects if a Nova 4 upgrade is anywhere on the horizon.
How can I help you explore Laravel packages today?