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

Laravel Nova Select2 Auto Complete Laravel Package

koss-shtukert/laravel-nova-select2-auto-complete

A Laravel Nova field that integrates Select2 auto-complete, letting users search and select related records through AJAX-powered dropdowns. Configurable endpoints and a smooth, user-friendly form experience for large datasets.

View on GitHub
Deep Wiki
Context7

Getting Started

Before you write any code, run two checks. This package's last release was 2020-02-28, which places it firmly in the Nova 2.x/3.x era. If your composer.json is on Nova 4+ or PHP 8.2+/Laravel 10+, assume it does not work until a throwaway-branch spike proves otherwise — Nova 4 and 5 reworked field internals and asset compilation. Second, diff the package's own composer.json constraints against your lockfile. The classic failure mode with stale Nova fields is a transitive pin that drags the whole monorepo back a Laravel or PHP minor, discovered only after composer update refuses to resolve.

Where to look first. Not the README — the field class itself. It is short (roughly 100–200 lines) and consists of a PHP Field subclass plus a compiled Vue/Select2 component. Read it in this order:

  1. The PHP field class — the fluent setters (search endpoint, result mapping, placeholder, min input length, debounce, multiple()), and how it passes config to the front end via meta()/withMeta() or the Field's jsonSerialize.
  2. The Vue component — how it instantiates Select2, what URL it hits, and how it maps the AJAX response into {id, text} pairs. This is the part that breaks across Nova majors.
  3. The published assets — check whether Select2 is bundled or CDN-loaded, and at what version.

First use case: a belongsTo on a large related table where the native dropdown has become unusable — a customer picker on an internal ops form, a SKU selector, an account lookup. Start with exactly one field on one internal screen.

Minimal steps (typical shape — confirm setter names against the field class):

// app/Nova/Order.php
public function fields(NovaRequest $request)
{
    return [
        // ... other fields
        Select2AutoComplete::make('Customer')
            ->searchEndpoint('/nova-api/customers/search')  // or model-relation based
            ->placeholder('Search customers…')
            ->minimumInputLength(2)
            ->debounce(400),
    ];
}

The dependency most people miss: the related Nova resource must declare

public static $search = ['name', 'email', 'account_number'];

Without it the AJAX endpoint returns nothing and you get a silently empty dropdown. This is a configuration dependency, not a bug, and it will burn support time if you don't call it out in the PR description. Verify it with a raw curl against the search route before you ever open the browser.

Pilot checklist before merging: field renders, typing returns results, selecting persists, and the value re-hydrates correctly when you re-open the record for editing (see Gotchas — this is where async fields usually fail).

Implementation Patterns

Single-select on a belongsTo. The bread-and-butter case. Point the field at the related resource's search endpoint, keep minimumInputLength(2) and a debounce around 300–500ms, and confirm the selected ID round-trips. On Index and Detail views, use the field's display formatter rather than letting Select2 markup leak into read-only contexts — a displayUsing() callback that resolves the label server-side is the pattern.

Multi-select on a belongsToMany. Flip the field to multiple mode (usually a ->multiple() setter) and make sure your search callback returns the full candidate set the pivot expects. Two things to verify explicitly: Select2's ordering of selected IDs, and that re-saving an unchanged record doesn't duplicate pivot rows. Test with 50+ selections, not 3.

Scoped / filtered options. The most common real requirement is "customers of this region" or "SKUs in this warehouse." Two approaches, both fine:

  • Query callback on the field — if the field exposes a ->query(fn ($q) => $q->where('region_id', $regionId)) style hook, use it. Cleanest, stays declarative.
  • Custom endpoint — register your own route in a small Nova-tool service provider (or routes/api.php behind nova middleware) and have it return a bounded, pre-mapped array. This is also the escape hatch for non-Eloquent sources: a remote API, a search index, a materialised view.

Whichever you pick, map the results server-side to {id, text}. Doing the mapping in JavaScript means every future change to your model touches the Vue layer, which is exactly the layer that's brittle across Nova upgrades.

A shared base field class. If you have more than two of these, don't copy-paste fields() declarations. Subclass the field into app/Nova/Fields/SearchableSelect.php with your house defaults already applied (debounce, min length, endpoint resolver, display formatter). Every call site then becomes a one-liner, and the day you have to patch around a Nova upgrade, you patch one file instead of nine.

Progressive rollout. Because the field is declarative and resource-scoped, put it on one form behind a Nova canSee() gate or a feature flag, ship it to your ops team, and measure time-per-entry before rolling it to the rest of the admin. This is genuinely a one-line revert if it misbehaves.

Testing. Two cheap tests pay for themselves:

  • Feature test on the search endpoint — authenticated, assert it returns a bounded array, assert it respects the $search columns, assert it 401/403s for a user without view permission on the related resource.
  • Render smoke test — a Nova Dusk or Pest browser test that just mounts the form and asserts the field renders. Nova majors break field components silently; this catches it in CI rather than in production on a Friday.

If you end up vendoring rather than installing, both tests move into your repo and survive the package's abandonment — which is the whole point.

Wiring into an existing form incrementally. Don't swap out every relationship select at once. The native Nova searchable() relationship field covers most belongsTo/belongsToMany cases and is vendor-supported; the correct sequencing is to prove searchable() fails on a named screen first, then reach for this. When you do replace one, keep the original field commented or in git history so you can A/B the UX.

Gotchas and Tips

The empty dropdown is almost always a missing $search. Symptom: field renders, typing does nothing, no error anywhere. Fix: public static $search = [...] on the related resource. Add it to your PR checklist. Also check nova:cache / config:cache — stale caches will make you think the field is broken when it isn't.

Version alignment is the real gate, not the install. composer require is trivial; resolution is not. Before anything else: check the package's Laravel/PHP constraints, check your Nova major, and confirm whether compiled assets ship in the package or require an npm build against a Nova-version-specific toolchain. If assets must be built, budget for the toolchain mismatch — a 2020 Vue build config will not drop into a 2024 Nova asset pipeline cleanly.

The v1.21 "zero index label" fix tells you something. That release fixed a bug where array keys starting at 0 produced wrong labels — i.e. the result-mapping layer is naive about numeric keys. If you map results yourself, always emit explicit id/text keys, never array_values()-style output, and never rely on array indices. It also tells you the test coverage is thin; assume other mapping edge cases (null labels, duplicate IDs, soft-deleted records) are unhandled.

Per-keystroke queries are the operational risk. Select2 fires an HTTP request per input event. Mitigate on three layers:

  • Client: minimumInputLength(2) plus a 300–500ms debounce.
  • Server: hard-limit results (20–50, never unbounded) and select only the columns you need — ->select('id', 'name'), not ->get().
  • Database: index the search columns. A LIKE '%term%' leading wildcard cannot use a B-tree index; either accept a bounded scan on a table you've sized, or move to a LIKE 'term%' prefix match / full-text index. Load-test at production row counts — autocomplete on 5,000 dev rows says nothing about 2 million.

Watch for N+1 inside the search callback. Accessors that lazy-load relations, appended attributes, or an accessor that hits the database will multiply by your result limit on every keystroke. Disable $appends for the query or eager-load explicitly.

Re-hydration on edit is the classic async-field failure. On an edit form, the field must render the label of the already-selected record without a round trip. If your search endpoint requires a minimum of 2 characters, an empty query won't return the current value and the field shows blank — which users read as data loss. Confirm the field fetches its initial selection by ID, or add a ->options() / initial-value hook. Test this explicitly, and test it under a user who lacks permission to list the whole related resource but can see the linked record.

Pin Select2 explicitly. The bundled version is unspecified in the package metadata, and a 2020-vintage Select2 is a latent front-end exposure if you publish assets as-is. Publish the assets, read the Select2 version banner, and pin it in your own build. Don't CDN-load it.

Debugging in order: (1) browser network tab — is a request firing, what's the payload, what's the status? (2) hit the endpoint directly with curl including session/auth headers; (3) if the endpoint is fine but the dropdown is empty, the problem is response shape vs. what the Vue component's processResults expects; (4) if nothing fires at all, it's the component not mounting — that's a Nova version incompatibility, not your config.

Extension points, in order of preference. Custom endpoint route → query callback → subclassed field in app/Nova/Fields/ → vendoring the whole package → forking. The last two are the same thing; the MIT license means you should default to vendoring over installing for an unmaintained package with zero dependents. Installing from Packagist inherits every future break with no upstream to notify you and no version to pin to.

Write down the removal path. Whatever you do, record in your dependency register: the owner, the Nova major it was verified against, the row-count threshold at which it's the right tool, and a review date. An internal admin screen that ops depends on becomes undeletable fast, and a 2020 field with one maintainer and no vendor to escalate to will eventually become an unowned ticket on every Nova upgrade. Budget a few hours per Nova major if you keep it; budget zero if you own the ~150 lines.

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.
abbasudo/artisan-route
mirvan/ippanel
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
adachsoft/api-version-bundle
cgv/localized-resource
noith/settings
kda/laravel-package