razorcreations/ajax-field
A Laravel Nova field that loads and updates its options over AJAX, making it easy to build dependent form fields—such as country/state or category/subcategory selects—where one field's value triggers a server request to refresh another's contents.
Architecture fit. razorcreations/ajax-field is a Laravel Nova field package, not an application dependency — it has no surface outside a Nova admin panel. Its job is narrow and well-bounded: replace a Select/BelongsTo that renders thousands of <option> nodes with a type-ahead picker that queries a JSON endpoint. Architecturally that is a presentation-layer swap over an existing ID column. Migrations, relationships, model events, and exists: validation stay untouched, which is what makes this low-blast-radius. From a product-architecture standpoint the fit is good only if three things are true: the product is committed to Nova as its admin framework, the pain is specifically large-option relationship fields, and we are not trying to make this field do anything else (multi-select, create-on-type, dependent cascades beyond a single parent value).
Integration feasibility. Feasible in a short timebox. The work is not the Composer require — it is building a search endpoint and getting its contract right. The package supplies the Vue component; we supply the controller, the route, the middleware, and the query scoping. Two unknowns dominate the estimate: the request parameter name the component sends (commonly q or search) and the exact JSON shape it expects back. Both live in resources/js/components/Field.vue and must be read, not assumed. A mismatch does not error — it returns a silently empty dropdown, which is the single hardest failure mode to diagnose. Treat "read three files before trusting the README" (src/AjaxField.php, Field.vue, composer.json) as a mandatory gate before any scheduling commitment.
Technical risk. Ranked by likelihood × impact:
| Risk | Likelihood | Impact | TPM posture |
|---|---|---|---|
| Silent empty dropdown from payload/param mismatch | High | Medium (schedule burn, not data loss) | Timebox a spike; verify against a 50-row table first |
| Permissive search endpoint leaks records | Medium | High (data exposure) | Blocking gate: server-side scoping to mirror Nova policy |
419/403 from endpoint outside Nova middleware |
Medium | Medium (works in testing, fails next morning) | Route must sit behind Nova's auth/session/CSRF group |
| No debounce / unbounded results → query storm | Medium | Medium (DB load, latency) | Inspect Field.vue; add ->limit(20) regardless |
LIKE '%term%' unindexable at scale |
Medium | Medium (feels slower than what it replaced) | Measure at realistic row counts, not dev fixtures |
| Nova 5 ends support (single maintainer, 13 stars, 0 dependents) | High eventually | Low if guardrailed | Exact version pin + named owner + fork plan |
| Unbuilt JS assets ship blank in prod | Low | High (broken admin) | Verify deploy path in a fresh environment |
The vendor-risk column is the one that needs a decision, not mitigation: with one maintainer, zero dependents, and an explicit Nova 3/4 target, Nova 5 is the likely end of the line. The code is small enough that a fork is a morning's work — so the real risk is not noticing the breakage, not being unable to fix it.
Key questions to resolve before committing.
composer show laravel/nova? Nova 5 is an evaluation blocker, not a footnote.Field.vue already debounce, enforce a minimum query length, and cancel stale requests?BelongsTo::make()->searchable() covers a surprising number of Nova 4 cases)?Stack fit. Requires Laravel + Nova 3 or 4, PHP and Nova constraints per composer.json, and a Nova resource with a large-table relationship field. Install is composer require razorcreations/ajax-field, plus php artisan nova:asset razorcreations/ajax-field if assets are not auto-registered — check composer.json and the README rather than guessing. The meaningful integration surface is our code: one search controller, one route inside Nova's middleware group, and one field declaration in fields().
Migration path. Treat this as a timeboxed pilot on one or two resources, not a rollout. The path:
1.* constraint. A routine composer update must not be able to change admin behaviour.Compatibility. Two contracts must line up: the Nova/PHP constraint in composer.json and the JSON contract between our controller and Field.vue. The second is the one that silently fails. Enforce it server-side by aliasing the label in SQL (->get(['id', 'name as label'])) rather than in a map() someone will forget to update, and cap the result set (->limit(20)) because the component may not paginate. Also verify whether JS assets ship prebuilt in dist/ — if not, the deploy needs a Node build step, and skipping it yields a blank input in production while working locally.
Sequencing. Front-load the two cheap unknowns before any stakeholder demo:
AjaxField.php, Field.vue, composer.json; confirm namespace, fluent setters, request param, and response shape. Output: a one-page contract note. No code.419/403 after a session rotation. Output: go/no-go recommendation.The CI test is the highest-leverage artifact here — it catches both middleware changes and Nova upgrades, converting "we didn't notice" into "the build failed."
Maintenance. Low ongoing effort, low bus factor. This is a dependency we must be prepared to fork, and the honest framing is that the fork is the plan, not the surprise. Required guardrails: exact version constraint, a named internal owner, a CI test hitting the endpoint, and a documented native fallback per field. The Vue component is also where any behaviour the package doesn't expose must be added — read Field.vue end to end once (ten minutes that replaces a day of trial-and-error) before considering a fork.
Support. No vendor SLA, no support contract, one maintainer. Internal support burden lands on whoever owns the search endpoint — which is fine, because that is ordinary Laravel controller code we wrote. The field's own behaviour (search box clearing after selection, focus sending an empty query and returning the first 20 rows, label rendering) is component-side and effectively unsupported; budget for reading and patching Vue rather than filing issues.
Scaling. AJAX search moves load from page render to the endpoint; it does not delete it. Every keystroke is a query unless throttled. Confirm in Field.vue whether there is debounce, a minimum query length, and cancellation of stale in-flight requests — missing any of these is typically a five-line patch worth doing rather than absorbing the load. Add ->limit(20) regardless. On the database side, LIKE '%term%' cannot use a B-tree index: invisible at a few thousand rows, painful at hundreds of thousands, where the "fast" AJAX field will feel slower than the dropdown it replaced. Prefer prefix search or whereFullText/Scout, accepting that behaviour diverges from substring matching. Select only the columns you send — never select * into a label payload.
Failure modes. Order of likelihood:
200 with the wrong JSON shape looks identical to "no matches". Debug order: php artisan route:list | grep search → hit the URL logged-in and inspect the raw payload → diff against Field.vue → only then look at the controller. The query is rarely the problem.419/403 after session rotation — a middleware bug, not a field bug. If the endpoint sits outside Nova's group it works in testing and fails the next morning; a 419 on an AJAX call is easy to miss in laravel.log, so watch the network tab.->where('team_id', $request->user()->team_id)).Ramp-up. Days, not weeks, for a Laravel-fluent engineer — the package is small and the integration surface is one controller, one route, one field declaration. The real ramp-up is reading Field.vue and establishing the response contract, which should be done once and written down. For the wider team, ramp-up is a paragraph: "this is a searchable picker that writes an ID into an existing column; here is the endpoint, here is the fallback field, here is the owner." Keep scope explicit in that same note — this is an accelerant for one field type in the internal admin panel: not customer-facing, not a strategic dependency, and not a reason to stay on Nova. That sentence is what keeps a future Nova 5 upgrade a bounded task rather than archaeology.
How can I help you explore Laravel packages today?