Autocomplete search follows the same basic pattern regardless of which front-end library renders it — an AJAX endpoint that returns matching records, and a widget that queries it as the user types — the differences below are all in how each library wires up that widget.
The shared backend endpoint
Route::get('/products/search', [ProductController::class, 'search']);
public function search(Request $request)
{
$products = Product::where('name', 'like', '%'.$request->get('term').'%')
->limit(10)
->get(['id', 'name']);
return response()->json($products);
}
This same endpoint (adjusted for the specific parameter name and response shape each library expects) is reused across all three examples below.
Select2
$('#product-select').select2({
ajax: {
url: '/products/search',
dataType: 'json',
delay: 250,
data: function (params) {
return { term: params.term };
},
processResults: function (data) {
return {
results: data.map(item => ({ id: item.id, text: item.name }))
};
}
}
});
Select2's delay option debounces the request, waiting 250ms after the user stops typing before actually firing the AJAX call — without this, every keystroke would trigger a separate request.
Customizing Select2 further: multi-select tags
$('#tag-select').select2({
tags: true,
tokenSeparators: [',', ' '],
ajax: { /* same ajax config as above */ }
});
Setting tags: true lets users both select from the AJAX results and type a brand-new value that doesn't exist yet — commonly paired with a backend that creates the new tag record on first use.
jQuery UI Autocomplete
$('#product-input').autocomplete({
source: function (request, response) {
$.getJSON('/products/search', { term: request.term }, function (data) {
response(data.map(item => ({ label: item.name, value: item.name, id: item.id })));
});
},
minLength: 2,
select: function (event, ui) {
$('#product_id').val(ui.item.id);
}
});
jQuery UI's version needs the hidden #product_id field set manually in the select callback — unlike Select2, it doesn't automatically track a separate "value" versus "display text" for you.
Typeahead.js
const products = new Bloodhound({
datumTokenizer: Bloodhound.tokenizers.whitespace,
queryTokenizer: Bloodhound.tokenizers.whitespace,
remote: {
url: '/products/search?term=%QUERY',
wildcard: '%QUERY'
}
});
$('#product-typeahead').typeahead(null, {
name: 'products',
source: products,
display: 'name'
});
Typeahead.js's Bloodhound suggestion engine adds built-in client-side caching of previous results — repeating a search term the user already typed doesn't necessarily re-hit the server.
Choosing between the three
Select2 is the most feature-complete out of the box (multi-select, tagging, styling that matches most admin themes) and is the most commonly reached-for choice for a Laravel admin panel specifically; jQuery UI's version is lighter weight if jQuery UI is already a dependency for other widgets on the page; Typeahead.js suits a simpler, more visually custom search box where Select2's default styling isn't a good fit.