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

Autocomplete Laravel Package

kerrialnewham/autocomplete

Laravel autocomplete package that makes it easy to add typeahead-style search to your app. Configure searchable model fields, query matching records, and return JSON results ready for any frontend autocomplete widget, with minimal setup and a clean, framework-friendly API.

View on GitHub
Deep Wiki
Context7

Getting Started

Read this first: the package is a Symfony bundle. kerrialnewham/autocomplete ships a Symfony bundle class, a Twig form helper, and a JSON endpoint, plus a Stimulus controller for the browser side. In a Laravel app, nothing in the PHP half will run — Laravel has no bundle registration, no services.yaml, no Twig. What you can reuse is the Stimulus controller and the request/response contract it speaks. Treat this package as a reference implementation to extract from, not a dependency to composer require.

Minimal path to a working search box in Laravel:

  1. Look at the Stimulus controller source first (typically assets/ or src/Resources/assets/ in the repo). This is the real spec — note the controller identifier, its static values (url, minLength, delay, maybe queryParam), and the exact JSON shape it expects back. Do not guess this; read it.
  2. Copy the controller into your app at resources/js/controllers/autocomplete_controller.js and fix the import path if it references a Symfony asset path.
  3. Register it with your existing Stimulus setup (Laravel's resources/js/bootstrap.js, or @hotwired/stimulus + Vite glob import).
  4. Write a Laravel route + controller that returns the JSON shape from step 1 — this replaces the Symfony endpoint entirely.
  5. Mark up the input in Blade with data-controller, data-autocomplete-url-value, and the target attributes the controller expects.

First real use case: a "find customer" box on an order-creation form. Type ≥2 chars → fetch /api/customers/search?q=… → render suggestions → clicking one writes the customer ID into a hidden field the form submits.

{{-- resources/views/components/autocomplete.blade.php --}}
<div data-controller="autocomplete"
     data-autocomplete-url-value="{{ $url }}"
     data-autocomplete-min-length-value="2">
    <input type="text"
           data-autocomplete-target="input"
           autocomplete="off"
           class="form-input w-full" />
    <input type="hidden" name="{{ $name }}" data-autocomplete-target="hidden" />
    <ul data-autocomplete-target="results" class="border rounded shadow"></ul>
</div>

Implementation Patterns

Own the endpoint. Match the contract exactly. The controller is a thin client; the PHP is where all your leverage lives. Return the same keys the controller reads and nothing more:

Route::get('/api/customers/search', function (Request $request) {
    $q = trim((string) $request->query('q', ''));

    if (strlen($q) < 2) {
        return response()->json(['results' => []]);
    }

    $customers = Customer::query()
        ->where('tenant_id', auth()->user()->tenant_id)
        ->where(fn ($query) => $query
            ->where('name', 'like', "{$q}%")
            ->orWhere('email', 'like', "{$q}%"))
        ->orderBy('name')
        ->limit(10)
        ->get(['id', 'name', 'email']);

    return response()->json([
        'results' => $customers->map(fn ($c) => [
            'value' => $c->id,
            'text'  => "{$c->name} ({$c->email})",
        ]),
    ]);
})->middleware(['auth'])->name('api.customers.search');

This is the entire reason to use server-side rendering over a client-side index: authorization, tenant scoping, and "search only records this user may see" happen in one place, in PHP, with your existing policies.

Wrap it in a Blade component. One <x-autocomplete> component used fifty times beats fifty hand-written data-attribute blocks, and gives you a single place to fix target names when you upgrade:

<x-autocomplete
    name="customer_id"
    :url="route('api.customers.search')"
    :value="old('customer_id', $order->customer_id ?? null)"
    :label="old('customer_name', $order->customer->name ?? null)" />

Have the component accept a label/value pair so edit forms render the already-selected item instead of an empty box.

Bind the ID, display the label. Never submit the visible text as the value. The hidden input is the source of truth; the visible input is ephemeral. On validation failure, old() must repopulate both or your users will lose their selection on every failed submit — feed the label back in from the relationship, as above.

Livewire / Alpine coexistence. If the enclosing form is a Livewire component, put wire:ignore on the autocomplete wrapper so Livewire's DOM diffing doesn't blow away the rendered suggestion list mid-fetch. Then bridge the hidden input back with wire:model on the hidden field, or dispatch a Livewire event from a custom select action in the controller.

Split the endpoint from the page. Keep the search route in routes/api.php (or a dedicated prefix) with throttle:60,1, so a held-down keystroke can't hammer your DB. The Stimulus delay value is a UX affordance, not a rate limit.

Cache aggressively when the data is stable. A Cache::remember("ac:{$tenant}:{$q}", 60, …) around the query is a one-line win on a hot search box, and the TTL gives you a natural staleness ceiling when a record is renamed.

Gotchas and Tips

  • The bundle PHP is dead weight, not a bug. composer require kerrialnewham/autocomplete in Laravel gets you a Bundle class that never registers and Twig templates you can't render. Copy the JS, don't require the package at runtime.
  • Target and value names are prefixed by the controller identifier. If the controller is autocomplete, the attribute is data-autocomplete-target="results" and data-autocomplete-url-value="…". Rename the file to customer-search_controller.js and every attribute silently stops working — Stimulus fails quietly, with a console warning at best.
  • CSRF. If you switch the fetch to POST (or add headers), you need X-CSRF-TOKEN from a meta tag. The Symfony original assumed its own session/CSRF story; verify what the controller actually sends before you assume GET.
  • Race conditions on fast typing. Two in-flight requests can resolve out of order and paint stale suggestions. If the upstream controller doesn't use an AbortController keyed to the latest term, add one — it's ten lines and it removes a whole class of "wrong result got clicked" bugs.
  • LIKE '%term%' won't use an index. Leading-wildcard matching is a full scan. Use term% where the UX allows, or move to a Postgres trigram / FULLTEXT index / Scout driver once the table grows past a few thousand rows. This is the first thing that will fall over in production.
  • Escape the rendered text. The controller almost certainly builds suggestion nodes from the JSON text field. If it uses innerHTML, an XSS payload in a customer name becomes a stored vulnerability. Check the DOM-write call in the controller source, and replace it with textContent if needed.
  • Accessibility is likely minimal. Expect no role="combobox", no aria-activedescendant, no arrow-key handling. Budget a day to add keyboard navigation and screen-reader attributes yourself; the package will not have solved this for you.
  • Asset path drift. Symfony bundles historically reference assets via AssetMapper or Webpack Encore paths. After copying, delete any import that resolves outside your Vite entrypoints, or the build fails with an opaque "failed to resolve" error.
  • Six stars, zero dependents, a single maintainer. Do not pin to a version and assume a fix will land. Vendor the controller into your repo, add a comment with the upstream commit hash, and write your own feature test against the JSON endpoint (auth, tenant scoping, the min-length short-circuit, the result cap). That test is the thing you'll actually rely on.
  • Extension point that matters: the JSON shape. Because you own both ends, you can add fields the controller ignores today (meta.total, avatar_url) and grow into them later without touching the search route's consumers. Keep the contract documented in the route's docblock.
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.
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
uni-method/symfony-lib