Using ProcessWire’s Selector API for Flexible Content Queries
Learn how ProcessWire’s selector API lets you build readable, decoupled content queries, see a paginated blog‑archive example, and understand the trade‑offs to keep your code maintainable.
18 Dec 2025, 02:16 UTC

Problem: dynamic content filtering without raw SQL
When building a site with ProcessWire, you often need to pull pages that match a mix of conditions –‑ for example, blog posts in a certain category that were published within the last six months. Writing raw SQL couples your code to the database schema and makes it easy to introduce syntax errors or security issues.
Thesis: the selector API gives a declarative, string‑based query language
ProcessWire’s selector API lets you express those conditions as a readable string. The API parses the string, builds optimized SQL behind the scenes, and returns a PageArray. Because the selector is decoupled from the table structure, you can change field names or types without rewriting the query logic.
Section 1 – Selector syntax overview
A selector is a comma‑separated list of field=value pairs. Operators include:
=exact match!=not equal<,>for numeric or date ranges~=contains (case‑insensitive)- Modifiers such as
limit,start,sort(prefix-for descending)
Fields are referenced by their ProcessWire name (e.g., template, title, created). Example: template=blog-post, limit=10, sort=-created returns the ten most recent blog‑post pages.
Section 2 – Worked example: paginated blog archive
Suppose you want to show news‑category blog posts from the last six months, ten per page. The selector string for the first page looks like this:
$selector = "template=blog-post, category=news, created>=2026-04-13, sort=-created, limit=10, start=0";
$posts = $pages->find($selector);
For subsequent pages you adjust the start value (e.g., start=10 for page two, start=20 for page three). The created>=2026-04-13 part uses a date string that ProcessWire automatically converts to a SQL date comparison.
To verify what SQL is generated, enable Debugger mode (Setup → Debugger → Debug mode) and run the selector. The Debugger panel displays the exact SQL statement and execution time, letting you confirm that the query uses indexes and respects any access checks.
Section 3 – Trade‑off and limitation
Very long selector strings become hard to read, especially when you combine many fields, nested conditions, or OR‑like logic (which requires parentheses and the | operator). In such cases the string can obscure performance bottlenecks; you should still inspect the generated SQL via the Debugger.
Another caveat: selectors bypass ProcessWire’s built‑in access checks unless you explicitly set the checkAccess flag to true. Forgetting this can unintentionally expose restricted pages. A safe pattern is to wrap the find call in a helper that forces access checking:
function findPages(string $selector): PageArray {
return $pages->find("$selector, checkAccess=1");
}
Section 4 – Actionable closing
To keep selectors maintainable:
- Encapsulate reusable selector logic in a small class or set of helper methods (as shown above).
- Test each helper with known pages: create a test page with specific field values, run the selector, and assert the result set matches expectations.
- Keep each selector focused on a single concern (e.g., filtering by date range) and combine them via
array_merge‑style concatenation only when necessary. - Always use the Debugger to view the SQL and query time during development; treat any unexpected full‑table scans as a signal to simplify the selector or add appropriate database indexes.
By treating the selector API as a first‑class query tool — and pairing it with simple verification steps — you gain the flexibility of a dynamic query language while staying clear of raw SQL pitfalls.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.