Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Nova Ajax Select Laravel Package

gabrielesbaiz/nova-ajax-select

Nova Ajax Select is a Laravel Nova field that adds AJAX-powered searchable select inputs, loading options asynchronously so forms can handle large datasets without bloating the page. It supports remote search, custom endpoints, and a smooth Nova-native experience.

View on GitHub
Deep Wiki
Context7

Getting Started

Before anything else, check for the asset collision. If alexwenzel/ajax-select is anywhere in your composer.json (directly or as a transitive dep), remove it first:

composer remove alexwenzel/ajax-select
php artisan nova:publish   # or at minimum clear Nova's compiled assets

Both packages register the same asset handle, and Nova resolves scripts with ->first(). The failure mode is silent and confusing: no PHP error, no console error, just [Vue warn]: Unknown custom element and a field that renders as an empty box. If you are migrating an app that had both installed, assume one of your two ajax fields has been dead for a while.

Then:

composer require gabrielesbaiz/nova-ajax-select

Requirements are steep and worth confirming up front: PHP 8.3+, Laravel 12 or 13, Nova 5. Nova is licensed separately, so this is not a drop-in for an app still on Laravel 11.

Where to read first: the Guide for a fresh install, but if you have any 1.x field in the codebase, read the Upgrade page before touching code — it is mostly a list of things to delete, and deleting them is the point of the release.

First use case to reach for: the classic "big select" that 1.x users hacked around — a BelongsTo whose options are too numerous to ship in the form payload, currently backed by a Text field for the ID plus a shadow Text field to show the label on index. In 2.0 that becomes one field: resolver scoped to the parent, server-side search, and labels on index/detail out of the box.

Implementation Patterns

Choose the resolver by shape, not by habit.

Source Use when
Closure Small, dynamically filtered set; you need request/user context
Eloquent model Large table, needs server-side search over named columns
Relation Options are genuinely scoped to a parent record (e.g. $tenant->users())
Backed enum Fixed vocabulary — no DB round trip at all

The enum resolver is underrated: it gives you Nova labels and validation for free and removes a config table you probably did not need.

Cascading selects — the headline change. In 1.x, parent() was a bespoke Vue watcher crawling the component tree, which is why it never fired inside action modals. It now registers a proper Nova dependency, so it composes:

Select::make('Country'),
NovaAjaxSelect::make('City')
    ->dependsOn(['Country'])   // preferred in 2.0
    ...

Prefer dependsOn() over parent() even though parent() still works — it is deprecated for 3.0, and it is the difference between a field that survives being moved into a panel and one that quietly stops refreshing.

Panels, pivot forms and action modals now just work. This is the release's real payoff. Delete the workarounds: the duplicated field definitions inside BelongsToMany pivot blocks, the "action modal variant" of the same field, and the custom action classes that re-declared fields solely to get a cascading select. Likewise delete canSee() shims that only existed because dependsOn() never fired — leaving them in place now hides fields the dependency would have shown.

Labels on index and detail. Drop the shadow Text field and the accessor. The field carries {value, display}, so index shows the label rather than the foreign key. Migrate the column over in one deploy rather than leaving both, or you will spend a sprint explaining why one resource shows "Acme Ltd" and its sibling shows 42.

Validation comes for free — stop materializing Rule::in. 2.0 validates submissions with a single membership query. That means you should delete:

  • generated Rule::in(Pluck::from(...)) rules,
  • sometimes|exists: rules layered on top,
  • and the withValidator closures that existed to keep those in sync with the option list.

The membership query respects the resolver's scope, so validation and options cannot drift. That is a real correctness win for tenant-scoped fields, where a hand-rolled Rule::in was one cache warm away from accepting another tenant's ID.

Server-side search: name your columns deliberately. Search runs on the DB against the columns you specify, which is what makes option sets of 50k rows viable. Name the human-facing columns (name, email, reference) and include the ID column only if users actually search by number — a broad column list turns every keystroke into a slow LIKE scan.

Caching is opt-in, and cacheScope() is not optional for multi-tenant. Turn caching on for stable option sets (enums, country lists, category trees) and always call cacheScope() with the tenant/user key when the resolver depends on it. Without it you will serve one tenant's filtered options to another — the kind of bug that only shows up in a demo to a customer.

No routes: stop writing them. 1.x exposed endpoints for option fetching. 2.0 rides Nova's own field-sync request, which is already authorized by Nova and already carries the request context. Anything you built as a custom endpoint, custom middleware, or canSee() gate around those routes is dead code — and if you wired rate limiting or audit logging to them, that telemetry disappears silently.

Gotchas and Tips

  • The asset handle collision is the number one support issue. Symptom: unknown component, no server error. Diagnosis: check composer show for alexwenzel/ajax-select, and if present, remove, republish, hard-refresh. Nova's ->first() resolution means the loser fails without a trace.
  • parent() works but is on the clock. 1.x calls compile fine, so there is no upgrade pressure — which is exactly how a deprecated API survives to become a 3.0 emergency. Sweep the codebase for parent() now; it is a one-line change per field.
  • The {value, display} payload is deprecated. If you have custom Vue components or JS that reads the field payload directly, it still works but will break at 3.0. Anything reading .display off a raw sync response is a ticking clock.
  • get() is deprecated but still functional. New code should use the current resolver API; old ->get(fn () => ...) closures keep working. Do not mix styles within a single resource — the deprecated path bypasses some of the new caching and validation wiring.
  • Search happens in the database, so the browser has nothing. Any custom JS that assumed it could enumerate the field's options (bulk-select helpers, client-side dedupe, "select all") has no list to work with. Fetch the subset you need server-side or drop the feature.
  • Debugging empty options: the resolver returning an empty collection is nearly always one of — (a) the parent value arrives null on first render (expected; guard the closure), (b) the dependency field name does not match the actual field name, or (c) a cacheScope() key is colliding. The absence of routes means there is no network tab entry to inspect; read Nova's field-sync payload instead.
  • Authorization still applies through the model. No routes does not mean no gates — Nova's sync request is authorized, but model policies and global scopes on the resolver's query still shape what is returned. A policy that filters a BelongsTo will make options vanish rather than 403.
  • readonly() plus a dependency: the field now participates in Nova's dependency system, so a readonly() field with a dependent select can briefly hold a stale value when the parent changes. Reset it explicitly in your resolver if the form allows the parent to move after the child is set.
  • Cache invalidation is manual. There is no observer wiring. For anything backed by an editable table, either keep caching off or bust the cache on save — a stale option list is a support ticket with a long tail.
  • Laravel 12/13 + PHP 8.3 floor is a hard wall. Apps on Laravel 11 or PHP 8.2 cannot upgrade, and there is no maintained 1.x-to-2.x bridge short of staying on 1.x. Check composer why-not before planning the sprint, not during it.
  • Extension point worth knowing: because resolution is a closure/model/relation/enum rather than a route, you can wrap a resolver in your own service class and reuse it across resources and action modals — that is the cleanest way to keep one option list canonical across the admin panel.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
tequilarapido/trackit
gregsphotography/site-builder-filament
cabaoctavio/laravel-query-log
gregsphotography/site-builder-components
io-digital/clickatell
snowsoft/laravel-admin
oxygen/auth
relicsoftware/barakah-payment-gateway
tobya/work-with-composer
equipc/laravel-email-validator
equipc/laravel-email-database-log
adedsec/nova-translated-labels
wazza/laravel-db-encryption
hokny/laravel-generator
vonsogt/backup
jengo/queues
jeremykenedy/laravel-observability
coleus/frame
thtg88/journalism
filagora/filagora-ai-forums