Reader Stacks

Autocomplete and Typeahead Search in Laravel: Select2, jQuery UI, and Typeahead.js

Three different front-end libraries for the same basic pattern: an AJAX endpoint returning matching records, and a widget that queries it as the user types.

Autocomplete and Typeahead Search in Laravel: Select2, jQuery UI, and Typeahead.js

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.