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 Bundle Laravel Package

fanforfun/select2-bundle

fanforfun/select2-bundle integrates the Select2 JavaScript library into PHP applications, giving forms searchable, AJAX-backed select boxes with tagging and multi-select support. A lightweight way to replace plain dropdowns with richer, user-friendly widgets.

View on GitHub
Deep Wiki
Context7

Getting Started

First, calibrate expectations: fanforfun/select2-bundle is a Symfony2 bundle, not a Laravel package. There is no service provider, no facade, no config/select2.php. Installing it via Composer into a Laravel app will do nothing useful — Symfony bundles are only auto-registered by Symfony's kernel. Check the repo before investing time:

  1. composer.json — look at the symfony/symfony constraint. If it pins ~2.x, the bundle predates Symfony 3 and is effectively abandoned (0 stars, 0 dependents confirms it).
  2. Resources/ — confirms it is an asset/form-helper bundle, not a data-layer library.
  3. DependencyInjection/ — the only real logic lives here, usually just registering a Twig extension or Assetic asset path.

Where to look first as a Laravel developer: don't. Treat this package as a reference implementation for how to wrap Select2 in a server-rendered framework, then reimplement the ~40 lines of glue natively in Laravel.

First use case to port: an "entity picker" — a <select> on an edit form that shows a label, stores an ID, and swaps to an AJAX-backed search once the option count exceeds a few hundred rows. That's 90% of what Select2 is used for in any Symfony or Laravel app.

Minimal Laravel equivalent:

npm install select2 jquery
// resources/js/app.js
import $ from 'jquery';
import 'select2';
import 'select2/dist/css/select2.css';

$('.select2').select2({ theme: 'default' });
<select name="customer_id" class="select2" data-url="{{ route('customers.search') }}">
  @if($customer) <option value="{{ $customer->id }}" selected>{{ $customer->name }}</option> @endif
</select>

Implementation Patterns

1. AJAX data source (the pattern the bundle was built for). Select2 expects a specific JSON shape — { results: [{ id, text }], pagination: { more: boolean } }. Implement it as an invokable controller, not a closure:

// app/Http/Controllers/CustomerSearchController.php
public function __invoke(Request $request)
{
    $term = $request->string('q');

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

    return response()->json([
        'results' => $customers->getCollection()
            ->map(fn ($c) => ['id' => $c->id, 'text' => $c->name]),
        'pagination' => ['more' => $customers->hasMorePages()],
    ]);
}

2. Init helper as a Blade component. Instead of copying $('.select2').select2() into every view, register a component that carries the defaults:

{{-- resources/views/components/select2.blade.php --}}
@props(['name', 'url' => null, 'multiple' => false, 'selected' => null])

<select
  name="{{ $name }}{{ $multiple ? '[]' : '' }}"
  {{ $attributes->merge(['class' => 'select2']) }}
  @if($multiple) multiple @endif
  @if($url) data-url="{{ $url }}" @endif
>
  {{ $slot }}
</select>

@push('scripts')
<script>
  $('[name="{{ $name }}{{ $multiple ? '[]' : '' }}"]').select2({
    width: '100%',
    ajax: @json($url ? ['url' => $url, 'dataType' => 'json', 'delay' => 250,
        'data' => "params => ({ q: params.term, page: params.page || 1 })",
        'processResults' => "data => data"] : null),
  });
</script>
@endpush

3. Repopulation after validation failure. This is where naive Select2 integrations break. old('customer_id') returns an ID with no label, so the select renders blank. Two fixes:

  • Server-side: eager-load the selected model in the controller's withInput path and pass its label to the view.
  • Client-side: use the templateSelection + a data-selected attribute containing pre-rendered <option> markup.
return back()->withInput()->with('selectedCustomer', Customer::find(old('customer_id')));

4. Multiple select + pivot sync. Name the field roles[] (Laravel's ->name attribute must end in []), validate with 'roles' => ['array'], 'roles.*' => ['exists:roles,id'], and sync(). Select2 will silently send a comma-joined string if you forget the brackets.

5. Tagging / create-on-the-fly. tags: true sends free text alongside IDs. Guard it with a custom validation rule that either resolves the string to an existing record or creates one inside a transaction — do not firstOrCreate in a loop inside the controller's happy path.

6. Livewire / Inertia. The bundle's Twig-helper approach has no analog. In Livewire use wire:ignore on the select and re-init select2 in a hook('select2:select') that calls $wire.set(...); in Inertia, mount on the form component's onMounted and destroy on unmount.

Gotchas and Tips

  • Select2 is not a Laravel package. Any attempt to composer require fanforfun/select2-bundle will pull in a bundle whose service provider is not discoverable by Laravel. You'll get a silent no-op plus a symfony/symfony dependency conflict that can wreck your lockfile. Vendor it as a reading reference only.
  • jQuery is mandatory. Select2 has no framework-agnostic build. If your app is jQuery-free, do not add jQuery just for one widget — use Tom Select, Choices.js, or Alpine + a headless combobox instead. This is the single biggest reason to skip this pattern in 2024+.
  • id/text are hard-coded keys. Returning {value, label} results in a blank dropdown with no error. Map explicitly in the controller.
  • Pagination is easy to get wrong. If you return pagination.more: true but never pass page back in data, infinite scroll loops on page 1 forever. Conversely, omitting pagination entirely makes Select2 think there's exactly one page.
  • CSRF on POST searches. If you use type: 'POST' for long queries, send X-CSRF-TOKEN from meta[name="csrf-token"]; a 419 surfaces as an empty result list, not a visible error.
  • Asset pipeline differences. The bundle targets Assetic, which was removed from Symfony in 3.0. There is zero transferable code for Vite/Laravel Mix. Reimplement assets through vite.config.js and import Select2 CSS explicitly — it is not auto-injected.
  • width: '100%' almost always needed inside Bootstrap/Tailwind forms, otherwise the widget collapses to the width of the longest option.
  • Re-init after DOM swaps. Any Livewire re-render, Turbo visit, or innerHTML replacement destroys Select2's generated markup while leaving the <select> intact. Destroy with $(el).select2('destroy') before re-init, or wrap in wire:ignore.
  • Debugging tips. In the browser console, $(el).data('select2') exposes the instance; network tab should show q and page params; if the AJAX call fires but nothing renders, log the raw response — a 302 to login (expired session) is the usual culprit.
  • Extension points in the original bundle (worth mirroring): a Twig/Blade helper for form rendering, a configurable default ajax.url, and a data-* attribute convention. In Laravel, the cleanest equivalent is a Blade component + a config key select2.defaults published via php artisan vendor:publish.
  • Maintenance verdict: 0 stars, 0 dependents, Symfony2-era. Do not adopt, do not pin, do not open issues — the pattern is worth studying for fifteen minutes, the code is not worth shipping.
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.
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings
coleus/apps
seampact/laravel
emmyconceptng/laravel-ng-postcode
jjsquady/round-robin