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

Select2Easy Laravel Package

gsferro/select2easy

View on GitHub
Deep Wiki
Context7

Getting Started

Install and pin. The package is small and has a single release (2024-09-02), so pin an explicit constraint rather than floating:

composer require gsferro/select2easy

Publish and read the config first. Run php artisan vendor:publish and look for the select2easy tag (config and, likely, JS assets/views). The published config is the real API surface — default per_page, minimum_input_length, searchable columns and the route prefix all live there. Before writing any code, open src/ and routes/ and confirm:

  • the service provider's published tags and bindings,
  • the trait/base-controller that builds the AJAX response,
  • whether a route is auto-registered (and under which middleware group — this matters, see Gotchas).

First use case: replacing a fat <select>. The canonical scenario is a form field backed by a 50k-row users table. Server side, the endpoint receives Select2's q, page and optional term params and must return {"results":[{"id":1,"text":"Ana"}],"pagination":{"more":true}}. The package exists to produce that envelope generically instead of you re-writing processResults per model:

// routes/web.php
Route::get('/select2/users', [UserSelectController::class, 'index'])
    ->name('select2.users');
$('#user_id').select2({
  ajax: {
    url: '{{ route('select2.users') }}',
    dataType: 'json',
    delay: 250,
    data: params => ({ q: params.term, page: params.page || 1 }),
    processResults: (data, params) => {
      params.page = params.page || 1;
      return { results: data.results, pagination: { more: data.pagination?.more } };
    },
    cache: true,
  },
  minimumInputLength: 2,
  placeholder: 'Search a user…',
});

That is the whole loop. Everything else is scoping and edge cases.

Implementation Patterns

Keep the endpoint in your app, not in the package. If the package auto-registers a catch-all route, prefer disabling it (config flag or by not loading routes) and defining explicit named routes in routes/web.php. You get per-model authorization, throttling and route-model awareness for free, and you can ->middleware('auth') on a case-by-case basis.

One controller per alias, or a registry. If the package is genuinely "global and generic," it likely supports a resource map (alias => [model, columns]). Use it for the boring cases and fall back to a real controller when the query has business rules (soft-deleted scoping, tenant, whereHas). Concretely:

public function index(Request $request)
{
    $term = trim((string) $request->input('q'));

    $users = User::query()
        ->select('id', 'name as text')
        ->when($term !== '', fn ($q) => $q->whereLike('name', "%{$term}%"))
        ->orderBy('name')
        ->paginate(20, ['*'], 'page', max(1, (int) $request->input('page')));

    return response()->json([
        'results'    => $users->items(),
        'pagination' => ['more' => $users->hasMorePages()],
    ]);
}

Always scope by the authenticated user. The q param comes from the browser; never let it widen the query set. ->where('tenant_id', auth()->user()->tenant_id) belongs in the endpoint, not in the JS data callback.

Pre-selected values on edit forms are a server problem. Select2 cannot turn an id into a label via AJAX on load. Render a single <option value="{{ $model->user_id }}" selected>{{ $model->user->name }}</option> inside the <select>, or add ajax.data that hydrates on init. This is the most common "it works on create but not on edit" bug.

Dependent selects chain cleanly: parent change clears the child, sets $child.data('parent', id), and the child's ajax.data callback sends parent_id. Enforce the same constraint server-side — a crafted request must not return children of a foreign tenant.

Return text as plain text. If the package's response formatter lets you return HTML, don't. Keep text escaped and let Select2 render it; if you must highlight the matched term, override escapeMarkup explicitly and sanitize.

Wrap it behind one method. Put the response shape in app/Support/Select2Response.php (or a small trait of your own) even if the package offers it. With 2 stars and 0 dependents, the cost of swapping the implementation later should be one file, not twenty controllers.

Gotchas and Tips

Maturity risk is the headline. Score 0.01, two stars, no dependents, last release September 2024. Read the source end-to-end before adopting — it's small enough to do in ten minutes. Treat it as a productivity shortcut for the response envelope, not as infrastructure you rely on for security (auth scoping, escaping).

Check the middleware on the served route. If the package registers its own route, it may sit outside auth or inside web only. An unauthenticated data-exposing endpoint for a user table is a real risk. Verify with php artisan route:list --name=select2.

Select2 sends q='' on open. With minimumInputLength: 0, the first request has no term. Decide deliberately whether that returns the first 20 rows (nice for small sets) or nothing (safe for large ones). Set minimumInputLength: 1 or 2 in the client and mirror the floor server-side.

pagination.more naming. Select2 4.x reads pagination.more; older examples use a top-level more or more: false. If you see the "Loading…" spinner never resolve, this key is almost always the cause. Confirm what the package emits dd()-ing the JSON directly:

curl -s 'http://app.test/select2/users?q=an&page=1' -H 'Accept: application/json'

LIKE '%term%' won't use an index. Fine at 10k rows, painful at 1M. Use whereLike (Laravel 11+), a prefix match term%, or a dedicated search column/Scout driver. Also cap per_page in config — clients can otherwise request huge pages if the package passes the param through.

cache: true on the client goes stale. After creating a record in a modal, the new item won't appear until reload. Clear with $el.select2('close') plus a cache-busting data param, or drop client caching for mutable tables.

CSRF and verbs. Prefer GET for read endpoints so browser/proxy caching and route() URL generation behave. If the package's route is POST, every AJAX call needs the X-CSRF-TOKEN header and you lose cacheability.

Debugging checklist. (1) DB::listen or ->dd() the query to confirm scoping; (2) assert the JSON contract in a feature test with assertJsonStructure(['results' => [['id','text']], 'pagination' => ['more']]); (3) check the browser Network tab for the exact params Select2 sent — term vs q mismatches are common when adapting examples from different Select2 versions.

Extension points. Publish the config and override the response formatter or the column resolver through the container binding rather than editing vendor/. If the package exposes a Blade directive or helper for the JS init, read what it generates before using it — hand-written $().select2({...}) is often clearer and always easier to debug.

When to skip it. If you're on Livewire, Alpine, or a JS framework, you want a component with the same semantics, not jQuery + Select2 4.1.0-rc.0. And if your app has exactly one or two autocomplete fields, the twenty-line controller above is less risk than a dependency with two stars.

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/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
skyraptor/chatter
edlugz/sasapay
abbasudo/artisan-route