A Laravel Livewire package for elegant, reactive model filtering with zero boilerplate. No more manual when() clauses!
- 🎯 Zero Boilerplate - No manual
when()clauses needed - ⚡ Reactive - Filters update automatically with Livewire
- 🔥 Elegant API - Same simple syntax:
Filter::like('name') - 🚀 PHP 8.5+ - Uses cutting-edge PHP features
#[\NoDiscard]attribute on all fluent methods (30+ methods)- Pipe operator
|>for data transformation pipelines - Laravel 12's
#[Scope]attribute for cleaner scopes
- 📦 Laravel 11 & 12 - Full support
- 🎨 Livewire 4 - Built for the latest version
- 🔍 Full-Text Search - PostgreSQL native support with GIN indexes
- 🔗 Relationships - Filter with
whereAny,whereAll,whereNone - 📅 Smart Dates - Carbon integration with
startOfDay/endOfDay - 🗄️ JSON Fields - Database-agnostic JSON filtering
- 🎭 Type-Safe - Full PHP 8.5 type coverage
composer require devaction-labs/livewire-filterableRequirements:
- PHP 8.5+
- Laravel 11 or 12
- Livewire 4
Note: PHP 8.5 is currently available on Linux. macOS and Windows support will be available when PHP 8.5 stable builds are released for those platforms.
namespace App\Models;
use DevactionLabs\LivewireFilterable\Traits\Filterable;
use DevactionLabs\LivewireFilterable\Concerns\HasCustomPagination;
use Illuminate\Database\Eloquent\Model;
class Customer extends Model
{
use Filterable;
use HasCustomPagination;
}namespace App\Livewire;
use App\Models\Customer;
use DevactionLabs\LivewireFilterable\Concerns\LivewireFilterable;
use DevactionLabs\LivewireFilterable\Filter;
use Livewire\Component;
class CustomerList extends Component
{
use LivewireFilterable;
// Public properties = automatic filters!
public string $name = '';
public string $legal_name = '';
public string $email = '';
public string $tax_id = '';
public string $tax_id_type = '';
public int $tenant_id = 0;
public function render()
{
$customers = Customer::query()
->with('tenant')
->filterable([ // Same elegant API!
Filter::ilike('name'),
Filter::ilike('legal_name'),
Filter::ilike('email'),
Filter::ilike('tax_id'),
Filter::exact('tax_id_type'),
Filter::exact('tenant_id'),
])
->orderBy('name')
->customPaginate();
return view('livewire.customer-list', compact('customers'));
}
}<div>
{{-- Filters automatically bind to public properties --}}
<input wire:model.live="name" type="text" placeholder="Nome">
<input wire:model.live="legal_name" type="text" placeholder="Razão Social">
<input wire:model.live="email" type="text" placeholder="Email">
<input wire:model.live="tax_id" type="text" placeholder="CPF/CNPJ">
<select wire:model.live="tax_id_type">
<option value="">Tipo</option>
<option value="cpf">CPF</option>
<option value="cnpj">CNPJ</option>
</select>
{{-- Results --}}
<div class="grid gap-4">
@foreach($customers as $customer)
<div class="border p-4 rounded">
<h3>{{ $customer->name }}</h3>
<p>{{ $customer->email }}</p>
</div>
@endforeach
</div>
{{ $customers->links() }}
</div>That's it! 🎉 No when() clauses, filters are applied automatically!
- 📘 How It Works - Understand how automatic property binding works
- 🚀 Laravel 12 & PHP 8.5 Features - Modern features used in the package
- 🔮 PHP 8.5 Full Features Example - Future implementation with all PHP 8.5 features
- 💡 Practical Examples - Real-world usage examples
O pacote suporta 3 tipos de paginação:
// 1. Padrão (com total count)
->customPaginate('paginate', 15)
// 2. Simples (sem total - mais rápido)
->customPaginate('simple', 20)
// 3. Cursor (mais performático - ideal para datasets grandes)
->customPaginate('cursor', 25)Controle dinâmico via Livewire:
class CustomerList extends Component
{
use LivewireFilterable;
public string $name = '';
public int $perPage = 15; // ✅ Dinâmico!
public function render()
{
$customers = Customer::query()
->filterable([Filter::ilike('name')])
->customPaginate('cursor', $this->perPage); // ✅ Usa a propriedade
}
}{{-- Usuário pode escolher --}}
<select wire:model.live="perPage">
<option value="10">10 por página</option>
<option value="25">25 por página</option>
<option value="50">50 por página</option>
<option value="100">100 por página</option>
</select>Filter::exact('status') // WHERE status = ?
Filter::notEquals('status') // WHERE status != ?
Filter::gt('amount') // WHERE amount > ?
Filter::gte('amount') // WHERE amount >= ?
Filter::lt('amount') // WHERE amount < ?
Filter::lte('amount') // WHERE amount <= ?
Filter::between('created_at') // WHERE created_at BETWEEN ? AND ?
Filter::like('name') // WHERE name LIKE %?%
Filter::ilike('email') // Case-insensitive (database-specific)
Filter::notLike('description') // WHERE description NOT LIKE %?%
Filter::startsWith('sku') // WHERE sku LIKE ?%
Filter::endsWith('domain') // WHERE domain LIKE %?
// PostgreSQL native with GIN index support
Filter::fullText(['title', 'content'], 'search')
->setFullTextLanguage('portuguese')
->setFullTextPrefixMatch(true)Filter::in('category_id') // WHERE category_id IN (?)
Filter::notIn('status') // WHERE status NOT IN (?)Filter::isNull('deleted_at') // WHERE deleted_at IS NULL
Filter::isNotNull('verified_at') // WHERE verified_at IS NOT NULL// Simple relationship
Filter::relationship('category', 'slug', '=', 'category')
->with()
// OR logic (whereAny)
Filter::relationship('tags', 'name')
->whereAny([
['name', '=', 'featured'],
['name', '=', 'sale'],
])
->with()
// AND logic (whereAll)
Filter::relationship('permissions', 'name')
->whereAll([
['name', '=', 'edit-posts'],
['is_active', '=', true],
])
->with()
// NOT logic (whereNone)
Filter::relationship('tags', 'is_banned')
->whereNone([
['is_banned', '=', true],
])
->with()Filter::json('attributes', 'color', '=', 'color')
->setDatabaseDriver('pgsql')
Filter::json('metadata', 'specs.weight', '>', 'min_weight')
->setDatabaseDriver('mysql')If your Livewire property name differs from the database column:
public string $searchName = '';
public string $customerEmail = '';
// Second parameter = Livewire property name
Filter::ilike('name', 'searchName')
Filter::ilike('email', 'customerEmail')public string $created_date = '';
Filter::exact('created_at', 'created_date')
->castDate()
->endOfDay() // Sets time to 23:59:59Use Livewire's #[Url] attribute to persist filters in the URL:
use Livewire\Attributes\Url;
class CustomerList extends Component
{
use LivewireFilterable;
#[Url]
public string $name = '';
#[Url]
public string $email = '';
#[Url(as: 'type')]
public string $tax_id_type = '';
}Now filters appear in URL: ?name=john&email=test@&type=cpf
Add debouncing to specific inputs in Blade:
<input wire:model.live.debounce.500ms="name" type="text">Or programmatically:
Filter::like('name')->debounce(500) // 500mspublic function clearFilters(): void
{
$this->reset(['name', 'email', 'tax_id']);
}In Blade:
<button wire:click="clearFilters">Limpar Filtros</button>class ProductList extends Component
{
use LivewireFilterable;
#[Url] public string $search = '';
#[Url] public array $price_range = [];
#[Url] public ?string $category = null;
#[Url] public array $tags = [];
#[Url] public string $created_date = '';
public function render()
{
$products = Product::query()
->filterable([
// Full-text search
Filter::fullText(['name', 'description'], 'search')
->setFullTextLanguage('portuguese'),
// Price range
Filter::between('price', 'price_range'),
// Category relationship
Filter::relationship('category', 'slug', '=', 'category')
->with(),
// Tags with OR logic
Filter::relationship('tags', 'name', 'IN', 'tags')
->with(),
// Date filter
Filter::exact('created_at', 'created_date')
->castDate()
->endOfDay(),
])
->orderBy('created_at', 'desc')
->customPaginate('paginate', 20);
return view('livewire.product-list', compact('products'));
}
public function clearFilters(): void
{
$this->reset(['search', 'price_range', 'category', 'tags', 'created_date']);
}
}// Migration
Schema::table('products', function (Blueprint $table) {
$table->tsvector('search_vector')->nullable();
});
DB::statement('CREATE INDEX products_search_idx ON products USING GIN(search_vector)');
// Update trigger
DB::statement("
CREATE TRIGGER products_search_update
BEFORE INSERT OR UPDATE ON products
FOR EACH ROW EXECUTE FUNCTION
tsvector_update_trigger(search_vector, 'pg_catalog.portuguese', name, description);
");
// Filter
Filter::fullText('search_vector', 'q')
->useTsVector()
->setDatabaseDriver('pgsql')Performance: 5ms vs 500ms on 1M rows (100x faster!)
Automatically adapts to your database:
- PostgreSQL: Native
ILIKE - MySQL:
LOWER()function - SQLite: Standard
LIKE(case-insensitive by default)
Filter::ilike('email') // Works on all databases!composer testRun specific test suites:
composer test:unit
composer test:types
composer test:lintThe MIT License (MIT). Please see License File for more information.
Please see CONTRIBUTING.md for details.
Livewire Filterable - Elegant filtering for Laravel Livewire 🚀
