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

Laravel Sieve Laravel Package

ukfast/laravel-sieve

ukfast/laravel-sieve adds declarative, reusable filtering and sorting to Laravel Eloquent queries, letting you map request query parameters to model scopes for cleaner, safer list endpoints and APIs.

View on GitHub
Deep Wiki
Context7

Getting Started

ans-group/laravel-sieve gives you convention-driven filtering, sorting, sparse fieldsets, relationship includes, and pagination straight from the query string — the Laravel equivalent of JSON:API-style query params, but for Eloquent.

v2.0.0 is a platform-support major. The release note only mentions "PHP 8.5 and Laravel 13", which almost certainly means the composer.json constraints were raised. Before anything else, check the diff for composer.json:

composer require ans-group/laravel-sieve:^2.0
composer show ans-group/laravel-sieve   # confirm the php/laravel/illuminate constraints

If you are still on PHP 8.2/8.3 or Laravel 11/12, stay on ^1.2 — a major bump is the maintainer's signal that older runtimes are no longer tested, even if the note doesn't spell out a drop.

Minimal setup:

  1. Install and publish the config: php artisan vendor:publish --tag=sieve-config (check the tag in the README — the config holds defaults for per-page size, max per-page, allowed relations, and the query-parameter names).
  2. Look at config/sieve.php first. Everything you'll want to tune day-to-day (default page size, the cap on per_page, the parameter names for filter/sort/include/fields/scope) lives there.
  3. Declare an allow-list on the model. Sieve is deny-by-default: only what you opt into is filterable/sortable/includable.

First use case — replacing a hand-rolled ->when($request->name, ...) index endpoint:

// app/Http/Controllers/Api/UserController.php
public function index()
{
    return UserResource::collection(
        Sieve::for(User::class)->paginate()
    );
}

That single call reads filter, sort, include, fields, and pagination params, validates them against the model's allow-list, and returns a paginator. GET /users?filter[status]=active&sort=-created_at&include=posts&fields[users]=id,name&per_page=25 works immediately.

Implementation Patterns

Model-level allow-lists. Keep the contract next to the model so it's reviewable in the same PR as schema changes:

class User extends Model
{
    public array $sieve = [
        'filters' => ['name', 'email', 'status', 'created_at'],
        'sorts'   => ['name', 'created_at'],
        'includes'=> ['posts', 'posts.comments'],
        'fields'  => ['id', 'name', 'email', 'status', 'created_at'],
    ];
}

Confirm the exact property/attribute name against the README for your version — it has changed shape across releases, and a silently-ignored allow-list is the most common "why is my filter returning everything?" bug.

Base controller / trait for consistency. Nearly every index endpoint looks the same. Extract it so pagination caps and response shape stay uniform:

trait SievesIndex
{
    protected function sieved(string $model)
    {
        return Sieve::for($model)
            ->when(! auth()->user()?->isAdmin(), fn ($s) => $s->where('owner_id', auth()->id()))
            ->paginate();
    }
}

Scopes as named business filters. Rather than exposing raw columns for complex cases (e.g. "overdue"), expose a scope: ?scope=overdue. That keeps the query-string surface small and lets you change the SQL later without breaking clients.

Sparse fieldsets to shrink payloads. fields[users]=id,name is the cheapest win for mobile clients and slow connections — pair it with API Resources that already respect $request->input('fields') or let Sieve trim the selected columns.

Relationship includes. ?include=posts.comments is the natural fix for N+1s in list endpoints, because clients declare what they need and Sieve eager-loads only allow-listed relations. Always bound include depth in config — unbounded nesting is a DoS vector.

Pair with policies, not config. Authorization belongs in a Policy/gate applied to the sieved query, not in the allow-list. The allow-list says "this column exists"; the policy says "this user may see it."

Form Requests for the non-Sieve bits. Validate the params Sieve doesn't own (e.g. date-range custom filters) in a FormRequest so failures return a clean 422 instead of a 500 from the query builder.

Gotchas and Tips

  • v2 upgrade checklist: diff composer.json for dropped PHP/Laravel versions, bump your CI matrix (add 8.5 + Laravel 13 jobs), run composer why-not if resolution stalls, and re-run your existing feature tests — the platform bump itself should be behaviour-neutral, so any test failure is a real regression, not "just the upgrade."
  • per_page is user-controlled. Always set max_per_page in config. A client asking for 10,000 rows will happily take your database down.
  • Filters on unindexed columns. Sieve will faithfully generate WHERE LOWER(name) LIKE '%…%' on a 5M-row table. Check EXPLAIN on your top filters and add indexes; consider restricting LIKE filters to a separate scope.
  • Silent ignore vs. hard failure. Decide early whether an unknown filter should 400 or be dropped. Sieve's default leanings differ by config key; make the choice explicit and test it, or clients will ship broken queries for months.
  • Debug by dumping SQL. Chain ->toSql() / ->dd() on the sieved builder, or enable query logging, when a filter "doesn't work" — nine times out of ten it's a missing allow-list entry or a param-name mismatch between client and config/sieve.php.
  • include + pagination. Eager-loading a large relation across a paginated page is fine; eager-loading it with per_page=500 is not. Cap page size before you cap include depth.
  • Sorting on related columns. sort=posts.created_at requires a join; verify the package version supports it and that the join doesn't multiply rows. Prefer sorts on the primary table.
  • Query-string caching. If you cache responses keyed on the URL, normalise param order (filter[a]&filter[b] vs filter[b]&filter[a]) or you'll get cache misses and inconsistent hit rates.
  • Don't leak columns via fields. A sparse fieldset request for a column not in the allow-list should error or be stripped — never silently pass through to select().
  • Extension points: custom filters/scopes registered on the model, config overrides per-environment, and wrapping the builder in a decorator are the safe ways to add behaviour. Avoid subclassing internals — majors like v2.0.0 are where private APIs move.
  • Pin the major. Because the 1.x → 2.x jump is purely a platform bump, ^2.0 in composer.json plus a CI job on PHP 8.5 / Laravel 13 is all you need; but for libraries that must support older Laravel, keep ^1.2 || ^2.0 with a documented matrix.
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.
payerurl/telegrambot-crypto-payment-instant-payout
dskripchenko/laravel-hybrid-search
riorizkyrainey/lara-ced
mehedimi/static-vel
zharp/zharp
nonsapiens/bigquery-model-sync
lgu-bayambang-ict-office/lgu-laravel-development-standards
andydefer/laravel-support
se7enxweb/flysystem
lampwick/laravel
jeslage/statamic-emojis
micahdshackelford/rmap-laravel
draftsmaninc/draftsman
square-bit/laravel-volition
himito/laravel-mailman
htinlynn/genius-helper
maco-studios/hephaestus
elmogy/larafast
xeviant/laravel-paystack
gorilladash/laravel-website-sdk