PHPackages                             pradeepdev001/laravel-smart-filter - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. pradeepdev001/laravel-smart-filter

ActiveLibrary

pradeepdev001/laravel-smart-filter
==================================

A powerful, elegant, and extensible Eloquent filtering package for Laravel 10, 11 &amp; 12.

00PHPCI passing

Since Aug 4Pushed todayCompare

[ Source](https://github.com/pradeepdev001/laravel-smart-filter)[ Packagist](https://packagist.org/packages/pradeepdev001/laravel-smart-filter)[ RSS](/packages/pradeepdev001-laravel-smart-filter/feed)WikiDiscussions main Synced today

READMEChangelogDependenciesVersions (1)Used By (0)

laravel-smart-filter
====================

[](#laravel-smart-filter)

[![Tests](https://github.com/pradeepdev001/laravel-smart-filter/actions/workflows/tests.yml/badge.svg)](https://github.com/pradeepdev001/laravel-smart-filter/actions)[![PHPStan](https://github.com/pradeepdev001/laravel-smart-filter/actions/workflows/static-analysis.yml/badge.svg)](https://github.com/pradeepdev001/laravel-smart-filter/actions)[![Latest Version on Packagist](https://camo.githubusercontent.com/0facaaba3cbe697831823d8eb04ce0d4b4512757f3dc7eb33c903da9166913b7/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f707261646565706465763030312f6c61726176656c2d736d6172742d66696c7465722e737667)](https://packagist.org/packages/pradeepdev001/laravel-smart-filter)[![PHP Version](https://camo.githubusercontent.com/cc9cdea9aa96b40a822425e981b0a030e3371202973c7d57b74e8e99834f81dc/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f7068702d253545382e312d626c7565)](https://packagist.org/packages/pradeepdev001/laravel-smart-filter)[![License: MIT](https://camo.githubusercontent.com/fdf2982b9f5d7489dcf44570e714e3a15fce6253e0cc6b5aa61a075aac2ff71b/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4c6963656e73652d4d49542d79656c6c6f772e737667)](LICENSE)

A powerful, elegant, and extensible Eloquent filtering package for Laravel 8, 9, 10, 11 &amp; 12.

Stop writing repetitive `when()` chains. Let your URL do the talking.

```
// Before
User::query()
    ->when($request->status, fn ($q, $v) => $q->where('status', $v))
    ->when($request->input('age>'), fn ($q, $v) => $q->where('age', '>', $v))
    ->when($request->country, fn ($q, $v) => $q->whereIn('country', explode(',', $v)))
    ->whereHas('posts', fn ($q) => $q->where('status', $request->posts_status))
    ->orderBy('created_at', 'desc')
    ->paginate();

// After
User::smartFilter()->paginate();
```

---

Requirements
------------

[](#requirements)

- PHP 8.1+
- Laravel 8, 9, 10, 11, or 12

---

Installation
------------

[](#installation)

```
composer require pradeepdev001/laravel-smart-filter
```

The service provider and facade are registered automatically via Laravel's package discovery.

**Publish the config (optional):**

```
php artisan vendor:publish --tag=smart-filter-config
```

---

Quick Start
-----------

[](#quick-start)

Add the `Filterable` trait to any Eloquent model:

```
use Pradeepdev\SmartFilter\Traits\Filterable;

class User extends Model
{
    use Filterable;

    // Whitelist filterable fields (empty = all allowed)
    protected array $filterable = ['name', 'email', 'status', 'age', 'country'];

    // Fields searched by ?search=
    protected array $searchable = ['name', 'email'];

    // Protect sensitive columns
    protected array $filterIgnore = ['password', 'remember_token'];

    // Map request params to real columns
    protected array $filterAliases = ['city' => 'address_city'];
}
```

Then in your controller:

```
public function index(): JsonResponse
{
    return User::smartFilter()->paginate();
}
```

Or chain on an existing query:

```
User::where('tenant_id', auth()->id())->smartFilter()->paginate();
```

---

Filter Syntax
-------------

[](#filter-syntax)

### Equals

[](#equals)

```
GET /users?status=active
GET /users?email=alice@example.com

```

### Not Equals

[](#not-equals)

```
GET /users?status!=inactive

```

### Comparisons

[](#comparisons)

```
GET /users?age>25
GET /users?price>=100
GET /users?age=1000

```

### Nested Relationships

[](#nested-relationships)

Chain as many levels deep as you need:

```
GET /users?company.name=Acme Corp
GET /users?company.city~New
GET /users?company.address.city=London

```

Each dot segment is a relationship method name. The last segment is the field on the related model's table.

### Many-to-Many

[](#many-to-many)

Works with `BelongsToMany` out of the box:

```
GET /users?roles.name=admin
GET /users?roles.name=in(admin,editor)

```

### Existence Checks

[](#existence-checks)

Check whether a relationship exists or not, without filtering on a specific field:

```
GET /users?posts=has           → users who have at least one post
GET /users?posts=doesntHave    → users who have no posts
GET /users?posts=orHas         → OR has at least one post

```

### Combining with Flat Filters

[](#combining-with-flat-filters)

Relationship filters and flat filters compose naturally with AND logic:

```
GET /users?status=active&posts.status=published
GET /users?country=india&roles.name=admin&sort=-created_at

```

### Model Setup for Relationships

[](#model-setup-for-relationships)

Define your Eloquent relationships as normal. No special configuration needed on the related model:

```
class User extends Model
{
    use Filterable;

    protected array $filterable = ['name', 'email', 'status'];

    public function posts(): HasMany
    {
        return $this->hasMany(Post::class);
    }

    public function company(): BelongsTo
    {
        return $this->belongsTo(Company::class);
    }

    public function roles(): BelongsToMany
    {
        return $this->belongsToMany(Role::class);
    }
}
```

> **Note:** The parent model's `$filterable` whitelist applies only to the parent's own columns. Fields inside a relationship subquery are validated independently, so `posts.title` will work even if `title` is not in the User model's `$filterable`.

---

Configuration
-------------

[](#configuration)

After publishing (`vendor:publish --tag=smart-filter-config`), `config/smart-filter.php` gives you global control:

```
return [
    'allowed_fields'    => [],               // Global whitelist (empty = all allowed)
    'ignored_fields'    => ['password'],     // Always blocked
    'aliases'           => [],               // ['city' => 'address_city']
    'searchable_fields' => [],               // Default ?search= fields
    'sort_param'        => 'sort',           // Query param for sorting
    'search_param'      => 'search',         // Query param for search
    'strict'            => false,            // Throw on unknown fields
    'operators'         => [],               // Custom operator classes
    'debug'             => false,            // Log parsed filters + SQL
];
```

---

Model-Level Configuration
-------------------------

[](#model-level-configuration)

Model properties always take precedence over global config:

PropertyTypeDescription`$filterable``list`Whitelisted fields. Empty = all allowed.`$filterIgnore``list`Always-blocked fields.`$filterAliases``array`Request param → real column name mapping.`$searchable``list`Fields used by `?search=`.`$filterStrict``bool`Throw exceptions instead of silently skipping.---

Custom Operators
----------------

[](#custom-operators)

Implement `OperatorContract` and register it in a service provider:

```
use Pradeepdev\SmartFilter\Facades\SmartFilter;
use Pradeepdev\SmartFilter\Contracts\OperatorContract;
use Pradeepdev\SmartFilter\DTOs\FilterInput;
use Illuminate\Database\Eloquent\Builder;

class StartsWithOperator implements OperatorContract
{
    public function apply(Builder $builder, FilterInput $input): Builder
    {
        return $builder->where($input->field, 'LIKE', $input->value . '%');
    }

    public function handles(): array
    {
        return ['starts_with'];
    }
}

// In AppServiceProvider::boot()
SmartFilter::extend(new StartsWithOperator());
```

Use it in the URL:

```
GET /users?name=starts_with(Jo)

```

Or register via config:

```
// config/smart-filter.php
'operators' => [App\Filters\StartsWithOperator::class],
```

---

Pagination
----------

[](#pagination)

SmartFilter is fully compatible with all Eloquent pagination methods:

```
User::smartFilter()->paginate(15);
User::smartFilter()->simplePaginate(15);
User::smartFilter()->cursorPaginate(15);
```

---

Testing Your Controllers
------------------------

[](#testing-your-controllers)

Pass a custom `Request` directly to `smartFilter()` to keep tests fast and HTTP-free:

```
$request = Request::create('/users', 'GET', [
    'status'       => 'active',
    'posts.status' => 'published',
    'sort'         => '-created_at',
]);

$results = User::smartFilter($request)->get();
```

---

Architecture
------------

[](#architecture)

```
src/
├── Contracts/      — FilterContract, OperatorContract, ParserContract, OperatorRegistryContract
├── DTOs/           — FilterInput, RelationFilterInput, SortInput, SearchInput (all readonly)
├── Enums/          — Operator (canonical names), SortDirection
├── Collections/    — FilterCollection (immutable typed container)
├── Parser/         — RequestParser (HTTP → FilterCollection, with dot-notation routing)
├── Operators/      — One class per operator (Equals, Like, In, Between, …)
├── Builders/       — FilterBuilder, RelationFilterApplier
├── Support/        — OperatorRegistry, FieldGuard
├── Traits/         — Filterable (adds scopeSmartFilter to any model)
├── Facades/        — SmartFilter
├── Exceptions/     — Typed, descriptive exceptions
└── SmartFilterServiceProvider.php

```

**Data flow:**

```
HTTP Request
     ↓
RequestParser  ──→  FilterCollection
                      ├── filters[]          (flat WHERE conditions)
                      ├── relationFilters[]  (dot-notation whereHas chains)
                      ├── sorts[]
                      └── search
     ↓
FieldGuard     ──→  alias resolution, allow/deny on flat fields
     ↓
FilterBuilder
  ├── flat filters    ──→  OperatorRegistry  ──→  Builder::where(...)
  ├── relation filters ──→  RelationFilterApplier  ──→  Builder::whereHas(...)
  ├── sorts           ──→  Builder::orderBy(...)
  └── search          ──→  Builder::where(orWhere...)
     ↓
Eloquent Builder (fully composed, ready for ->get() / ->paginate())

```

---

Roadmap
-------

[](#roadmap)

PhaseStatusFeature1✅ CompleteCore operators, sorting, search2✅ CompleteRelationship filtering (whereHas, has, nested)3🔄 PlannedDate filters (today, last\_week, this\_month…)4🔄 PlannedJSON column filtering5✅ CompleteCustom operator registration6✅ CompleteFull config publishing7🔄 PlannedPerformance optimisation &amp; caching8🔄 PlannedMacros, IDE helpers, PHPStan types9✅ PartialPest test suite (105 tests, 210 assertions)10✅ CompleteREADME &amp; documentation---

FAQ
---

[](#faq)

**Q: Does this prevent SQL injection?**
A: Yes. All filter values are passed via PDO bound parameters through Eloquent. Field names are validated against an allow-list before interpolation. Input is additionally sanitised at parse time (null bytes and control characters stripped).

**Q: Can I use this without the trait?**
A: Yes. Resolve `FilterBuilder` from the container and call `apply($builder, $collection)` directly.

**Q: What happens with unknown filter params?**
A: By default they are silently skipped. Set `$filterStrict = true` on the model (or `'strict' => true` in config) to throw `InvalidFilterFieldException` instead.

**Q: Does the parent model's `$filterable` block relationship fields?**
A: No. `$filterable` on the parent model only applies to the parent's own columns. Fields inside a relationship subquery (`posts.title`, `company.city`) use a permissive guard so they're never accidentally blocked by the parent's whitelist.

**Q: Which relationship types are supported?**
A: `HasMany`, `BelongsTo`, `BelongsToMany`, and `HasOne`. Any Eloquent relationship that supports `whereHas` works.

**Q: Can I nest relationships more than one level?**
A: Yes, unlimited depth. `?company.address.city=London` works, as does `?org.department.team.lead_name=Alice`.

---

Contributing
------------

[](#contributing)

See [CONTRIBUTING.md](CONTRIBUTING.md).

Security
--------

[](#security)

See [SECURITY.md](SECURITY.md). Please do not open public issues for vulnerabilities.

Changelog
---------

[](#changelog)

See [CHANGELOG.md](CHANGELOG.md).

License
-------

[](#license)

MIT — see [LICENSE](LICENSE).

###  Health Score

21

—

LowBetter than 17% of packages

Maintenance65

Regular maintenance activity

Popularity0

Limited adoption so far

Community9

Small or concentrated contributor base

Maturity11

Early-stage or recently created project

 Bus Factor1

Top contributor holds 81.8% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/94212000?v=4)[pradeepdev001](/maintainers/pradeepdev001)[@pradeepdev001](https://github.com/pradeepdev001)

---

Top Contributors

[![pradeepkays](https://avatars.githubusercontent.com/u/102803828?v=4)](https://github.com/pradeepkays "pradeepkays (9 commits)")[![dependabot[bot]](https://avatars.githubusercontent.com/in/29110?v=4)](https://github.com/dependabot[bot] "dependabot[bot] (1 commits)")[![pradeepdev001](https://avatars.githubusercontent.com/u/94212000?v=4)](https://github.com/pradeepdev001 "pradeepdev001 (1 commits)")

### Embed Badge

![Health badge](/badges/pradeepdev001-laravel-smart-filter/health.svg)

```
[![Health](https://phpackages.com/badges/pradeepdev001-laravel-smart-filter/health.svg)](https://phpackages.com/packages/pradeepdev001-laravel-smart-filter)
```

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
