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

Ajax Select Laravel Package

alexwenzel/ajax-select

A Laravel package for AJAX-powered select inputs that let users search and load options from a remote endpoint as they type, handy for large datasets like users or products where preloading every option isn't practical.

View on GitHub
Deep Wiki
Context7

Getting Started

alexwenzel/ajax-select is a Laravel Nova form field that renders a <select> whose options are fetched over HTTP at render time instead of being serialized into the Nova index/show payload. Its secondary purpose is the "child select" — a select whose option list is reloaded whenever another field on the same form changes (the classic Country → City / Category → Subcategory cascade).

Minimal setup:

composer require alexwenzel/ajax-select
php artisan nova:publish   # only if the package ships views/assets you override
php artisan optimize:clear # config + view cache, so Nova picks up the new field

You need Nova installed and a working nova guard first — this package is a field, not a standalone tool, so nothing works without a Nova resource to hang it on.

Then add the field to a resource:

use Alexwenzel\AjaxSelect\AjaxSelect;

public function fields(NovaRequest $request)
{
    return [
        AjaxSelect::make('Category', 'category_id')
            ->endpoint(route('api.categories.index'))   // returns [{value, label}, ...]
            ->rules('required', 'exists:categories,id'),
    ];
}

Your endpoint is just a normal route returning JSON:

Route::get('/api/categories', fn () => Category::query()
    ->when(request('q'), fn ($q, $term) => $q->where('name', 'like', "%{$term}%"))
    ->limit(50)
    ->get(['id as value', 'name as label']));

Look first at the field class in src/ (for the fluent API: endpoint, dependsOn, searchable, placeholder, etc.) and the compiled component in dist/js. The README is thin for a package this size, so reading the field class is faster than guessing.

First use case: a two-level cascade on a create form — a plain Select for the parent, an AjaxSelect for the child. It gives you the "child list reloads" behavior without writing a custom Vue field.

Implementation Patterns

Parent/child cascade. The parent stays a normal Nova Select (its options are small and static). The child declares the dependency and the endpoint reads the parent value from the request:

Select::make('Country', 'country_id')->options(Country::pluck('name', 'id')),

AjaxSelect::make('City', 'city_id')
    ->endpoint(route('api.cities.index'))
    ->dependsOn('country_id'),   // prop name on the parent field
Route::get('/api/cities', fn (Request $request) => City::query()
    ->where('country_id', $request->query('country_id'))
    ->get(['id as value', 'name as label']));

Keep the query-parameter key identical to the parent field's attribute name — that string is the contract between the two fields, and it's the single most common thing to get wrong.

Search-as-you-type for large tables. For anything over a few hundred rows, don't dump the whole table. Use the search term, limit(50), and index the column you filter on. This is the pattern that justifies the package: a BelongsTo on a 200k-row users table will otherwise bloat every form render with a giant <option> list.

Edit forms need preloaded labels. On update, the field has no options until the first fetch resolves, so the current value renders blank or as a raw ID. Either:

  • render an initial option server-side via the field's options()/withMeta() for the current value, or
  • add ->displayUsing(fn ($id) => User::find($id)?->name) for index/detail views.

Do this deliberately — reviewers notice a blank required field on the edit screen.

Endpoint reuse with a type discriminator. One controller for many selects:

Route::get('/api/options/{type}', OptionsController::class);

Switch on $type inside the controller. Fewer routes, one auth policy, one place to add caching.

Validation lives in the field rules, not the endpoint. Ajax only drives the UI; the browser can post anything. Always pair with ->rules('exists:...') (or a Rule::in closure). Treat the endpoint as a hint, the rule as the guarantee.

Tenant / permission scoping. Every endpoint must filter by the authenticated user's tenant. An Ajax endpoint is effectively a small read API — a user who can't see a record in Nova shouldn't be able to enumerate it via ?q=.

Testing. Field-level tests use Nova::actingAs($user) and assert the route returns the right shape; the component itself is best covered with one Dusk smoke test of the cascade (change parent → child options change).

Gotchas and Tips

  • Middleware is the failure mode. The endpoint must be in the web group (session + CSRF) and behind your auth/nova guard. If it's in api, Nova's axios call will 401/419 and the select just silently shows nothing. Open the network tab before touching the Vue component.
  • Nothing in the log when it "doesn't work." A 200 with the wrong JSON shape looks identical to an empty list. Return [{value, label}] exactly; if the component expects id/text, the select will be blank with a green response.
  • Stale request races. Rapid parent changes fire overlapping requests; the last response to arrive wins. Debounce and cancel the previous request in the component, or include the parent value in the response and discard mismatches.
  • Clear the child when the parent empties. Otherwise the old city_id stays selected and gets saved against a different country. Set the child value to null on parent change.
  • Cache carefully. A Cache::remember on the endpoint is a cheap win for hot, rarely-changing lists, but key it on the search term and the tenant, and invalidate on save. A cache keyed only on q is a cross-tenant data leak.
  • Pin the version. Two stars and zero dependents means this is effectively a solo-maintained package with one release (2025-03-25). Check its composer.json Nova constraint against your Nova major, and lock it. Verify the compiled assets match your Nova version's build pipeline.
  • Index and detail views. Ajax fields are form-first; they generally don't render nicely in index tables. Plan on ->displayUsing() or ->hideFromIndex() and a real column via BelongsTo for sorting/filtering.
  • Sorting and filtering. A raw Ajax select stores a plain column, so Nova's index sorting works, but Nova's built-in filter UI won't know about it. Add a BooleanFilter/SelectFilter that maps to the same column if you need it.
  • Extension points are thin but open. If behavior is missing, the field class exposes component() and withMeta(), and the Vue component lives in resources/js. Forking or vendoring the component is realistic here — at this size you may end up maintaining it, so budget for that before adopting.
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.
coleus/frame
thtg88/journalism
filagora/filagora-ai-forums
rlacerda83/query-parser
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings