abbasudo/laravel-purity
Laravel Purity is an elegant filtering and sorting package for Laravel Eloquent. Add filter() to any query and let frontend users filter and sort results through simple URL query string parameters, with no extra boilerplate.
Architecture fit. Purity is a thin request-to-query translator layered on Eloquent. It introduces no tables, migrations, config file, queue workers, or external services; Post::filter() simply returns a mutated query builder. Blast radius is therefore small and reversible — you can remove the call and the application still compiles. It composes cleanly with existing local scopes, global scopes, policies, and API resources, and it fits the standard "thin controller, query in the model/scope" Laravel idiom that most teams already use.
The architectural caveat to flag explicitly: Purity reads the HTTP query string from inside the data layer. That is the entire value proposition (zero boilerplate), but it means your public filtering grammar (?filters[title][$contains]=…, ?sort=-created_at) becomes a de facto product contract owned partly by a third-party package. Decide up front that this grammar is versioned API surface, documented in your OpenAPI spec, not an implementation detail. If you ever replace Purity, you must preserve the wire format or version the endpoint.
Integration feasibility. High. Install is a single Composer require; enablement is opt-in per query. The real work is not technical — it is the allowlist design. Every model exposed to filtering needs an explicit $filterable/allowed-fields declaration (ideally via getFilterables() or equivalent), and every sortable column needs whitelisting. This is a per-model configuration task that scales linearly with the number of exposed entities, and it is the main driver of effort estimation. Budget roughly half a day per model for definition plus tests, not half a day for the whole rollout.
Technical risk.
filter() lacks an allowlist. There is history in the changelog of related fixes (unquoted column names in DefaultSort, a body-vs-query-string scoping fix, $notIn corrections) — good that they were fixed, informative that this class of bug recurs.$contains differ across MySQL, PostgreSQL, MariaDB, and SQLite (case sensitivity, LIKE vs ILIKE). Multiple changelog entries show this being patched repeatedly. If you run different engines per environment, treat filter behavior as needing per-driver test coverage.?sort=-some_unindexed_column on a large table is a table scan triggered by a client, not by your code. This is the largest production risk and it is not visible in code review.Key questions.
Stack fit. Native fit for a Laravel API (REST or as a Livewire data source). It assumes Eloquent as the data access layer and does not interfere with authentication, authorization, validation, or serialization — those stay where they are. It is not a fit for raw-query/DB-facade code paths, for GraphQL where the query language is already the contract, or for endpoints where the client is another service whose query semantics you control directly.
Migration path. Low-risk, incremental, reversible:
There is no data migration and no downtime. Rollback is removing the filter() call from a query or reverting the Composer change; no schema state is created.
Compatibility. Verify against your exact PHP/Laravel pair before committing — the package tracks Laravel majors closely but not instantaneously, so a team mid-upgrade to the newest Laravel major should confirm the release that added support is the one they pin. Confirm database driver parity if you run MySQL in production and SQLite in tests; run the filter test suite against the production engine. Confirm behavior with relation-based sorting if you use it, since that generates joins that interact with existing select/with clauses.
Sequencing. Allowlist design and performance guardrails must land before the first user-facing endpoint, not after. Concretely: (1) define the public filter grammar and write it into the API docs; (2) build the allowlist contract and a test that asserts unfilterable columns are rejected; (3) pilot; (4) add query-count and slow-query instrumentation; (5) roll out per endpoint in order of ascending data volume, leaving the largest tables last so you have evidence before touching them.
Maintenance. Low ongoing code maintenance (no config drift, no migrations, no background processes) but non-zero dependency maintenance: pin versions, review release notes on upgrade, and re-run driver-specific filter tests after any Laravel major. The allowlist itself is living configuration — it will drift as models gain columns, and without an ownership rule it silently becomes either over-permissive (security) or stale (product gaps). Assign a named owner.
Support. Support burden concentrates in two places: "why doesn't my filter work" (usually a missing allowlist entry, a renamed field, or an unlisted operator) and "this endpoint got slow" (usually an unindexed sort). Both are answerable with documentation, not engineering. Write a one-page internal runbook mapping query-string patterns to the allowlist entries that must exist, and instrument rejected-filter counts — a spike there is your fastest signal of a client/contract mismatch. The package's docs site is decent but you should not depend on upstream for internal guidance; the maintainer is a single person with no commercial support tier implied.
Scaling. Two independent axes. Request volume scales trivially — this is pure query-builder manipulation with negligible PHP overhead, no I/O, no shared state. Data volume is the real constraint: filter/sort permutations multiply, index coverage cannot follow, and offset pagination on filtered results degrades predictably. Practical guardrails: cap page size, prefer cursor pagination on high-volume endpoints, restrict sortable columns to indexed ones, and set a hard row-count or time threshold above which a given table is not exposed to arbitrary filtering. Where relation sorting is used, the generated joins can amplify cost — measure before enabling on large tables.
Failure modes.
$contains behaves differently in staging (SQLite) than production (MySQL/PG), usually around case sensitivity. Tests must run on the production engine.Ramp-up. A mid-level Laravel developer is productive on Purity in under a day — the surface is filter(), sort(), and an allowlist declaration. The ramp-up cost is not the package; it is the two disciplines it demands: allowlist hygiene and query performance literacy. Budget a short team session on both, and require the first allowlist PR to be reviewed by whoever owns database performance. Plan for roughly one engineering day of setup plus per-model definition work, and treat that per-model figure as the honest estimate for the whole initiative.
How can I help you explore Laravel packages today?