Using AEM QueryBuilder for complex faceted search queries
Faceted search in AEM works best when every user selection becomes a small, explicit instruction to the JCR. The QueryBuilder servlet, commonly reached through /bin/querybuilder.json, accepts predicates for path, node type, property values, full-text terms, ordering and pagination. Combined with facet counts, these predicates let visitors browse a catalogue without inventing exact search phrases.
That flexibility also creates a familiar failure mode: filters multiply, query strings become opaque and performance changes as content grows. A robust design starts with a contract between the search interface and the repository. It defines which facets are multi-select, how values are normalised, which combinations are valid and how quickly the service must respond.
Success depends on more than endpoint syntax. The content model must carry stable filter attributes, indexes must support common predicates, and authorisation must be evaluated for every result. Otherwise, a technically valid query can still return incomplete counts, slow pages or content the current visitor should not see.
For Australian teams, that contract should include familiar market details: Australian dollars and GST-inclusive pricing, NSW, VIC, QLD, WA and SA filters, and dates displayed as day/month/year. Searches may need to serve audiences from Sydney and Melbourne to remote areas around Perth or Adelaide, so latency and mobile behaviour deserve equal attention.
Define the Search and Facet Contract
Begin with decisions rather than endpoint parameters. A facet is a grouped filter dimension, so list each facet, its allowed values and whether it is single- or multi-select. Distinguish filters, such as state or asset type, from ranking inputs, such as a full-text phrase or recency. Define a stable response shape for results, total hits, applied filters and facet counts.
The response contract matters because QueryBuilder supplies the filtered JCR result set, while facet totals may require an indexed search service or a separate aggregation request. Test whether counts include the current selections. Many faceted interfaces either retain counts for sibling choices or show zero for values excluded by another filter, and that policy should be explicit.
Where AEM sits beside a Microsoft-based service layer, the C# developer event programme provides useful background for adjacent engineering teams. The .NET team can consume a stable facet model without knowing QueryBuilder's parameter names.
Model Content Around Filterable Attributes
Choose a content model that supports filtering without scattering conventions across packages. Store controlled values consistently, such as au, full-time or case-study, rather than mixing display labels, abbreviations and legacy spellings in one property. Keep the display label in metadata and send a canonical value to QueryBuilder.
Facets such as price, date and score need predictable data types. Currency should be stored as a numeric minor-unit value plus an explicit currency code, while dates should use a consistent ISO representation in the repository even when Australian interfaces display 04/07/2026. Multi-valued tags need a deliberate policy: either select any matching value or define a separate aggregate property.
For teams combining editorial content with IoT or event feeds, the ISORC 2015 archive offers background on connected, event-oriented computing. Such feeds often need a projection into stable, indexable JCR properties rather than volatile sensor values copied onto every asset.
Translate Filters into QueryBuilder Predicates
Map each canonical facet to a QueryBuilder predicate. Use path to constrain the content scope, type to identify pages or assets, and numbered property predicates for exact filters. Set p.limit deliberately and choose orderby only when the result sequence adds value.
A typical JSON request might look like this:
{
"path": "/content/dam/insights/en",
"type": "dam:Asset",
"property.1": "myapp:region",
"property.1.value": "au",
"property.1.operation": "equals",
"property.2": "myapp:contentType",
"property.2.value": "case-study",
"property.2.operation": "equals",
"p.limit": "20",
"p.offset": "0"
}
Numbered property predicates are combined with AND. Supported operations include equality, existence and comparisons, but operation names must be selected from code, never passed through unchanged from the browser. For free-text input, keep the full-text predicate separate and confirm how it interacts with property filters and ranking.
Compose Complex Filter Groups Safely
QueryBuilder groups express OR conditions, but the interface should rarely expose that power directly. Map a multi-select facet to one known group, such as region equals au OR region equals apac, then AND that group with filters for content type, date and state. Build the group structure in server-side code so clients cannot create arbitrary predicates.
Avoid a query that combines every possible Boolean expression. The number of unique filters grows quickly, and each additional path, wildcard or text condition changes the cost. Query once for the selected result set, calculate facet counts from a compatible index, and use a small cache for common combinations. If counts come from another service, include a timestamp or version so stale figures are identifiable.
Search and navigation also require different priorities. A relevance-ranked phrase should influence the order of matching results, not silently decide which categories exist. Keep the chosen filters visible, offer a clear way to remove them, and preserve a stable result order when the user changes count or page size. Those details reduce confusing shifts on mobile, where narrow screens make removed filters easy to miss.
Secure User-Controlled Search Parameters
The principal risk is not malformed JSON but an attacker steering the query towards unauthorised paths, expensive properties or unrestricted result sets. Accept only facet identifiers known to the application and translate each one into a server-owned property, operation and value. Do not pass raw path, type, orderby, p.limit or p.offset parameters from the browser.
Apply repository permissions through the authenticated session or a least-privilege service identity, then filter any downstream projection by the same rules. Bound page size and offset, reject unknown parameters, and log rejected filter identifiers rather than their full values. If logs include IP addresses or search histories, assess them against the Australian Privacy Principles and minimise retention.
The archived AEM security guidance is a practical companion to this threat model. Reviewing it alongside QueryBuilder input handling helps teams consider the broader request path: authentication, authorisation, configuration and error behaviour, not just predicate construction.
Measure, Index, and Scale the Query Path
Measure each important filter combination, not just an empty query. Record execution time, result count, cache hit rate and timeout rate by path and predicate shape. Use AEM's query explanation and logging facilities to see whether Oak can use an aggregate, property or full-text index. On AEM 6.5, review Oak index definitions; in AEM as a Cloud Service, validate the predicates against the managed indexing model.
If a predicate is routinely unindexed, change the content model or supported query shape rather than adding an index without understanding its write cost. Set explicit targets for paged and faceted requests, and test them from Australian network conditions, including a mobile connection in regional Western Australia. Keep the query scope narrow, avoid leading wildcards and cap expensive searches.
Selective hit-count behaviour, such as p.hits=selective, can avoid exact totals when an estimate is acceptable, but the interface must label that difference honestly. Volatile state has another scaling rule: keep it out of the content repository unless it belongs with the content. The CIRCUIT DynamoDB session persistence example shows how a separate session service can preserve state while AEM scales independently.
Roll Out Search as an Iterative Product
Launch with a representative content set and a small set of meaningful facets. Add contract tests for parameter mapping, authorisation, empty results and count consistency. Compare QueryBuilder responses with expected JCR results, then monitor real searches for zero-result filters, abandoned facets and slow combinations. Retire fields that users ignore or that make every query more expensive.
Reviewing the CIRCUIT speaker profiles can help teams identify practitioners who have worked through AEM search, content modelling and platform constraints. The 2015 and 2016 session recordings and peer discussions are especially valuable when a production incident exposes a gap that a unit test cannot reproduce.
Choose one high-value search journey today: catalogue filtering, partner discovery, support content or media search. Write its facet contract, map every value to an allow-listed predicate, and test the slowest Australian audience path before adding more controls. That focused exercise turns an opaque endpoint into a searchable product with measurable behaviour.
Run a small production pilot with one audience, capture baseline latency and count accuracy, and review the next release against those recorded baselines. Keep the contract, query samples and security decisions together so every future change can be traced and challenged.