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

Select2 Laravel Package

ivaynberg/select2

Select2 is a jQuery-based replacement for native select boxes, adding searchable dropdowns, remote data loading, infinite scrolling, tagging, and custom templating. It works with any PHP or Laravel app and drops easily into forms to create fast, accessible, user-friendly select inputs.

Deep Wiki
Context7

Getting Started

This is the Composer distribution of the Select2 jQuery plugin (3.x-era), not a PHP package. There is no service provider, no facade, and no config file — it just drops select2.js / select2.css into vendor/ivaynberg/select2. Treat it as static front-end assets you happen to install with composer require.

Minimal setup:

  1. composer require ivaynberg/select2
  2. Publish or copy the assets into public/ (there is no vendor:publish tag by default — either reference vendor/ivaynberg/select2/select2.js directly, or copy select2.js and select2.css into public/vendor/select2/):
    cp -r vendor/ivaynberg/select2 public/vendor/select2
    
  3. Load jQuery first, then Select2, in your layout:
    <link rel="stylesheet" href="{{ asset('vendor/select2/select2.css') }}">
    <script src="{{ asset('vendor/jquery/jquery.min.js') }}"></script>
    <script src="{{ asset('vendor/select2/select2.js') }}"></script>
    
  4. Initialize against a normal <select>:
    {{ Form::select('user_id', $users, null, ['class' => 'select2']) }}
    
    @push('scripts')
    <script>
      $('.select2').select2({ width: '100%', placeholder: 'Search…', allowClear: true });
    </script>
    @endpush
    

Look here first: the plugin's ajax option and the expected JSON shape ({ results: [{ id, text }], more: true }). That contract between a Laravel route and Select2 is 90% of the day-to-day work.

First real use case: replace a <select> holding thousands of rows with an AJAX-backed searchable select. Don't render 10k <option>s — point Select2 at a route.

Implementation Patterns

AJAX search backed by a controller.

// routes/web.php
Route::get('/api/users', [UserSearchController::class, 'index'])->name('users.search');

// UserSearchController
public function index(Request $request)
{
    $term = $request->input('q');
    $page = (int) $request->input('page', 1);

    $users = User::query()
        ->when($term, fn ($q) => $q->where('name', 'like', "%{$term}%"))
        ->orderBy('name')
        ->paginate(20, ['id', 'name'], 'page', $page);

    return response()->json([
        'results' => $users->getCollection()->map(fn ($u) => [
            'id'   => $u->id,
            'text' => $u->name,
        ]),
        'more' => $users->hasMorePages(),
    ]);
}
$('#user_id').select2({
  width: '100%',
  minimumInputLength: 2,
  ajax: {
    url: '{{ route('users.search') }}',
    dataType: 'json',
    delay: 250,
    data: params => ({ q: params.term, page: params.page || 1 }),
    processResults: (data, params) => ({
      results: data.results,
      pagination: { more: data.more }
    }),
    cache: true
  }
});

Dependent / cascading selects. Clear and re-init the child on parent change; don't try to mutate options by hand.

$('#country').on('change', function () {
  $('#city').val(null).trigger('change');
  $('#city').select2({ ajax: { url: `/api/cities?country=${this.value}`, ... } });
});

Tagging / multi-value input. tags: true with a plain <select multiple> is the simplest path; validate the array server-side ('tags.*' => 'string|max:40') since Select2 will happily create free-text values.

Pre-selection on edit forms. Select2 can't infer a label from an ID. Either render the single matching <option selected> server-side, or seed it explicitly:

$('#user_id').append(new Option('{{ $user->name }}', '{{ $user->id }}', true, true)).trigger('change');

Blade integration. Keep init code out of partials. Use @push('scripts') + @stack('scripts') in the layout, and guard against double-init with .hasClass('select2-hidden-accessible') when the same view re-renders (e.g. after validation errors).

Asset pipeline. If you're on Vite, don't require this Composer package — install the npm select2 package instead and import it in resources/js/app.js. Use the Composer package only on legacy/asset-pipeline setups where a plain <script> tag is fine.

Gotchas and Tips

  • This is Select2 3.x, not 4.x. The APIs differ materially: initSelection (3.x) vs ajax.processResults handling of id/text (4.x); formatResult/formatSelection (3.x) vs templateResult/templateSelection (4.x). Most tutorials you'll find online are for 4.x and will silently do nothing here. Check $.fn.select2.defaults or the version string before copying snippets.
  • jQuery load order is non-negotiable. Select2 executes against $ at parse time. If your bundle loads jQuery after, you get select2 is not a function.
  • CSRF. The AJAX call above is a GET, so you're fine. If you switch to POST to avoid long URLs/caching, add X-CSRF-TOKEN via $.ajaxSetup or the ajax.headers option, otherwise Laravel returns 419 and Select2 just shows "The results could not be loaded."
  • "No results" on a working endpoint is almost always a shape mismatch. Laravel's paginate() returns {data: [...], total: ..., current_page: ...} — Select2 wants results + more, and each item needs literal id and text keys. Map them explicitly.
  • Validation errors wipe the widget. After a redirect-back, old('user_id') is a scalar/array, not a rendered option, so the label disappears. Re-seed the option server-side (see above) or the select renders blank while still holding the value.
  • width: '100%' or nothing lines up. Select2 defaults to resolve, which breaks in flex/grid layouts. Also set dropdownParent: $('#modal') for selects inside Bootstrap modals, or the dropdown renders behind the modal.
  • allowClear requires a placeholder — a placeholder attribute and allowClear: true for single selects; otherwise the clear "×" never appears.
  • XSS in templates. If you use templateResult/formatResult with escapeMarkup: m => m and interpolate user-controlled text, you've reintroduced XSS. Keep the default escaping or use $('<div>').text(...).
  • Repopulating multi-selects. Select2 stores nothing in the DOM beyond the underlying <select>; if you're swapping form sections with Livewire/Alpine/partial reloads, re-init and re-append options after every morph, or the widget is empty.
  • Extension points are thin. No provider means no config surface: no global defaults file, no publish tag, no middleware hooks. Standardize by writing your own small resources/js/select2-init.js that reads data-* attributes off the select (data-url, data-placeholder, data-min-length) and initializes everything in one place — that's the closest thing to a "config" this package offers.
  • Maintenance signal. 20 stars, unknown repo: this is an abandoned mirror of an abandoned 3.x line. For new work, plan the migration to the npm select2@4 package (or a modern alternative) and treat the Composer install as a stopgap while you keep legacy forms working.
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.
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource
noith/settings