devaction-labs/livewire-filterable
| Install | |
|---|---|
composer require devaction-labs/livewire-filterable |
|
| Latest Version: | v1.0.0.0 |
| PHP: | ^8.5.0 |
| Laravel: | 12 |
| License: | MIT |
| Last Updated: | Jan 27, 2026 |
| Links: | GitHub · Packagist |
A Laravel Livewire package for elegant, reactive model filtering with zero boilerplate. No more manual when() clauses!
✨ Features
- 🎯 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
📦 Installation
composer require devaction-labs/livewire-filterable
Requirements:
- 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.
🚀 Quick Start
1. Add traits to your Model
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;
}
2. Create a Livewire Component
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'));
}
}
3. Create the Blade View
<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!
📚 Documentation
- 📘 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
🔄 Paginação
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>
📖 Available Filters
Basic Comparison Filters
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 ?
Text Search Filters
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 %?
Full-Text Search
// PostgreSQL native with GIN index support
Filter::fullText(['title', 'content'], 'search')
->setFullTextLanguage('portuguese')
->setFullTextPrefixMatch(true)
Array Filters
Filter::in('category_id') // WHERE category_id IN (?)
Filter::notIn('status') // WHERE status NOT IN (?)
Null Filters
Filter::isNull('deleted_at') // WHERE deleted_at IS NULL
Filter::isNotNull('verified_at') // WHERE verified_at IS NOT NULL
Relationship Filters
// 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()
JSON Field Filters
Filter::json('attributes', 'color', '=', 'color')
->setDatabaseDriver('pgsql')
Filter::json('metadata', 'specs.weight', '>', 'min_weight')
->setDatabaseDriver('mysql')
🎯 Custom Property Names
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')
📅 Date Handling
public string $created_date = '';
Filter::exact('created_at', 'created_date')
->castDate()
->endOfDay() // Sets time to 23:59:59
🔗 URL Persistence
Use 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
⏱️ Debouncing
Add debouncing to specific inputs in Blade:
<input wire:model.live.debounce.500ms="name" type="text">
Or programmatically:
Filter::like('name')->debounce(500) // 500ms
🧹 Clear Filters
public function clearFilters(): void
{
$this->reset(['name', 'email', 'tax_id']);
}
In Blade:
<button wire:click="clearFilters">Limpar Filtros</button>
🎨 Complete Example
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']);
}
}
🗄️ Database-Specific Features
PostgreSQL Full-Text Search with GIN Index
// 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!)
Case-Insensitive Search (ILIKE)
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!
🧪 Testing
composer test
Run specific test suites:
composer test:unit
composer test:types
composer test:lint
📝 License
The MIT License (MIT). Please see License File for more information.
🤝 Contributing
Please see CONTRIBUTING.md for details.
🙏 Credits
Livewire Filterable - Elegant filtering for Laravel Livewire 🚀
Related Packages
A clean, convention-based way to extract Eloquent query filters into dedicated f...
Filterable, sortable, searchable index listing for laravel models
Complete query builder system with filters, sorting, search, and pagination. Bui...
Filtrado, ordenamiento y paginacion declarativa para modelos Eloquent. Auto-desc...
Version History
| Version | Released | PHP | Laravel | License |
|---|---|---|---|---|
| v1.0.0.0 | ^8.5.0 | ^12 | MIT |