Adobe AEM

AEM Query Builder: The Ultimate Recipe Book

13 min read

A comprehensive library of 20+ real-world, production-ready AEM Query Builder snippets, recipes, and complex examples for AEM 6.5 and AEM as a Cloud Service.

AEMQuery BuilderSearchSnippetsReference
AEM Query Builder: The Ultimate Recipe Book

If you remember only one thing from this guide, let it be this: mastering the AEM Query Builder is not about knowing the syntax, it is about understanding how Oak translates your queries into repository traversal and index utilization. Most teams get this wrong because they copy-paste snippets from Stack Overflow without considering the architectural implications of their search parameters. When you execute a poorly formed query in a production environment with millions of nodes, you do not just get a slow response—you risk degrading the entire authoring experience, bringing your AEM instances to a grinding halt due to massive traversal warnings and memory exhaustion.

In this ultimate recipe book, we are skipping the basic tutorials and diving straight into the deep end. This guide covers a vast library of 20+ real-world, production-ready Query Builder recipes, ranging from finding unactivated pages and specific Content Fragment models to complex full-text searches with property boosting and advanced pagination. Every single recipe is accompanied by its HTTP parameter representation, the Java API map equivalent, and the crucial context on how to ensure it performs at scale.

This post serves as the practical companion piece to my theoretical Query Builder Complete Reference. To fully understand the underlying mechanics, be sure to cross-reference that guide, along with our discussions on the JCR and Oak Repository, the AEM Backend Development Complete Guide, and AEM Performance Troubleshooting.

The Query Builder Debugger

Before we dive into the recipes, you need a workbench. AEM provides a built-in tool that is absolutely indispensable for constructing and testing your queries safely before they ever touch your Java code.

The Query Builder Debugger is located at /libs/cq/search/content/querydebug.html on your AEM author instance.

Why You Must Use the Debugger

Never write a Query Builder query blindly in your Java code. Always test it in the debugger first. The debugger provides three critical pieces of information:

  1. The Result Set: Does the query actually return the nodes you expect?
  2. Execution Time: How many milliseconds did it take? If it takes more than 100ms on a local instance, it will be disastrous in production.
  3. The XPath Translation: Query Builder is just an abstraction layer. AEM translates your Query Builder parameters into an XPath query, which Oak then executes. The debugger shows you this exact XPath query, allowing you to use the Oak Index Manager (/libs/granite/operations/content/diagnosis/tool.html/granite_oakindex) to Explain the query and see which Lucene or Property index is being invoked.

In the debugger, you simply paste the HTTP parameter format (the key=value pairs) into the text area, hit "Execute", and analyze the output.


The Recipe Library

Below is an exhaustive collection of Query Builder recipes that solve real enterprise requirements. For every recipe, I provide the HTTP parameter format (which you can paste directly into the Query Builder Debugger) and a brief explanation of how it operates under the hood.

1. Find All Pages Using a Specific Template

One of the most common requirements during a migration or a redesign is identifying every page that utilizes a legacy or specific template.

type=cq:Page
path=/content/we-retail
property=jcr:content/cq:template
property.value=/conf/we-retail/settings/wcm/templates/hero-page
p.limit=-1

Explanation: By searching for the cq:Page type and restricting the path, we limit the search space. The property predicate reaches into the jcr:content child node (which holds the actual page properties) and checks the cq:template property. Setting p.limit=-1 returns all results, but be cautious with this in production if the result set is massive.

2. Find All Pages Missing a Specific Property

Content governance often requires finding pages where authors have forgotten to fill in crucial SEO metadata, such as the jcr:description.

type=cq:Page
path=/content/wknd
property=jcr:content/jcr:description
property.operation=not
p.limit=100

Explanation: The property.operation=not is the secret sauce here. It instructs Oak to find nodes where the specified property does not exist. This is a notoriously expensive query if not bounded by a strict path because Oak cannot effectively index the absence of a property. Always run this within a narrow path context.

3. Find Assets Modified in the Last 7 Days by a Specific User

When debugging workflow issues or tracking down rogue asset modifications, you need to pinpoint exact changes made by a specific author within a timeframe.

type=dam:Asset
path=/content/dam
relativedaterange.property=jcr:content/cq:lastModified
relativedaterange.lowerBound=-7d
property=jcr:content/cq:lastModifiedBy
property.value=admin
p.limit=50

Explanation: This recipe uses the relativedaterange predicate, which is far superior to passing absolute dates when you are building dynamic dashboards. -7d means "seven days ago until now." We combine this with a standard property check on cq:lastModifiedBy.

4. Find Pages Containing a Specific Component

If you are deprecating an old component (e.g., an outdated carousel), you need to find every page that embeds it. Since components are nested deep within the layout container, we cannot just search the page node.

path=/content/wknd
type=nt:unstructured
property=sling:resourceType
property.value=wknd/components/carousel
p.limit=-1

Explanation: Notice we are searching for type=nt:unstructured because components are dropped as unstructured nodes on the page. We search for the specific sling:resourceType. Once you get the result in Java, you can call Hit.getResource().getParent().getParent() (or use PageManager.getContainingPage(resource)) to find the actual page.

5. Complex Full-Text Search with Property Boosting

Building a site search requires full-text capabilities. But you want matches in the page title to rank higher than matches in the page description.

type=cq:Page
path=/content/my-site
fulltext=architecture
fulltext.relPath=jcr:content
property=jcr:content/jcr:title
property.value=architecture
property.boost=10.0
p.limit=20
orderby=@jcr:score
orderby.sort=desc

Explanation: We execute a fulltext search on the jcr:content node. We artificially boost the score if the jcr:title matches the term by using property.boost=10.0. We then explicitly order by @jcr:score descending to ensure the most relevant results appear first. This relies heavily on a properly configured Lucene index in Oak.

6. Find Tags in a Specific Namespace

When building custom tagging dialogs, you often need to fetch all tags under a specific namespace (e.g., wknd-shared).

type=cq:Tag
path=/content/cq:tags/wknd-shared
p.limit=-1
orderby=@jcr:title
orderby.sort=asc

Explanation: AEM stores tags as cq:Tag nodes under /content/cq:tags. By setting the path to the namespace root, we retrieve all child tags. Ordering by @jcr:title ensures they are displayed alphabetically to the author.

7. Find Unactivated (Never Published) Pages

Content audits often reveal hundreds of pages that were created but never published. Let's find them.

type=cq:Page
path=/content/campaigns
property=jcr:content/cq:lastReplicationAction
property.operation=not
p.limit=-1

Explanation: When a page is published, AEM adds the cq:lastReplicationAction property (usually with the value "Activate"). If this property is entirely missing, the page has never been published.

8. Find Pages Where a Property Equals X OR Y

Sometimes you need to find pages that match one of several criteria, such as multiple specific templates.

type=cq:Page
path=/content/wknd
group.1_property=jcr:content/cq:template
group.1_property.value=/conf/wknd/settings/wcm/templates/article-page
group.2_property=jcr:content/cq:template
group.2_property.value=/conf/wknd/settings/wcm/templates/news-page
group.p.or=true
p.limit=50

Explanation: The group predicate is your best friend for complex logical operations. By assigning properties to group.1_... and group.2_..., and setting group.p.or=true, we instruct the Query Builder to execute an OR condition.

9. Find All Content Fragments of a Specific Model

With Headless AEM being the norm, finding Content Fragments by their schema (Model) is a daily task.

type=dam:Asset
path=/content/dam/wknd
property=jcr:content/data/cq:model
property.value=/conf/wknd/settings/dam/cfm/models/article
p.limit=-1

Explanation: Content Fragments are stored as dam:Asset nodes. The crucial distinction is that their structural schema path is stored at jcr:content/data/cq:model. If you need to deeply understand Content Fragments, refer to my Content Fragments Complete Guide.

10. Complex Pagination Queries

When exposing AEM content via a custom API, you must paginate the results to avoid memory spikes.

type=cq:Page
path=/content/my-site
orderby=@jcr:content/cq:lastModified
orderby.sort=desc
p.offset=20
p.limit=10

Explanation: This translates to "Give me page 3" (assuming 10 results per page). p.limit restricts the batch size, and p.offset skips the first 20 results. Crucial Warning: Deep pagination (e.g., p.offset=10000) is extremely slow in Oak because Oak still has to traverse and count the first 10,000 nodes. Use keyset pagination if you have massive datasets.

11. Find Pages Modified After a Specific Date

Useful for generating sitemaps or synchronizing data with external systems.

type=cq:Page
path=/content/we-retail
daterange.property=jcr:content/cq:lastModified
daterange.lowerBound=2023-01-01T00:00:00.000+00:00
p.limit=-1

Explanation: Unlike relativedaterange, the daterange predicate takes strict ISO-8601 timestamps. The lowerBound means "everything after this date."

12. Find Assets of a Specific MIME Type (e.g., PDF)

A common requirement when building an asset download center.

type=dam:Asset
path=/content/dam/documents
property=jcr:content/metadata/dc:format
property.value=application/pdf
p.limit=100

Explanation: Asset metadata is stored under jcr:content/metadata. The standard Dublin Core format property (dc:format) holds the MIME type.

13. Find Pages with Specific JCR Node Names

Sometimes authors create pages with completely unstructured titles, but you know the node name follows a convention (e.g., ends in -2023).

type=cq:Page
path=/content/campaigns
nodename=*-2023
p.limit=-1

Explanation: The nodename predicate allows for wildcard matching directly against the node name (the URL segment), avoiding the need to query properties.

14. Find Nodes Using Multiple Property Constraints

Find all active promotions that are marked as featured.

type=cq:Page
path=/content/promotions
1_property=jcr:content/active
1_property.value=true
2_property=jcr:content/featured
2_property.value=true
p.limit=50

Explanation: By prefixing properties with 1_ and 2_, Query Builder automatically applies an AND condition between them.

15. Find Pages by Tag (Exact Match vs Descendants)

AEM tagging is hierarchical. If you search for apparel, do you also want apparel/shirts?

type=cq:Page
path=/content/we-retail
tagid.property=jcr:content/cq:tags
tagid=we-retail:apparel
p.limit=50

Explanation: The tagid predicate inherently understands AEM's tag hierarchy. By default, it will return pages tagged with apparel AND any of its children. This is much more powerful than doing a simple string match on the cq:tags array.

16. Find Checked-out Nodes

In environments where JCR versioning is heavily used, finding nodes locked by authors is crucial for maintenance.

type=cq:Page
path=/content/site
property=jcr:content/jcr:isCheckedOut
property.value=true
p.limit=-1

Explanation: When a page is versioned or locked, the jcr:isCheckedOut boolean flag is manipulated.

17. Find Content Fragments Containing Specific Elements

You want to find all Article fragments where the "author" field contains "Aman".

type=dam:Asset
path=/content/dam/fragments
property=jcr:content/data/cq:model
property.value=/conf/wknd/settings/dam/cfm/models/article
1_property=jcr:content/data/master/author
1_property.value=Aman
p.limit=-1

Explanation: We first restrict by the model type. Then we dive into the specific variation node (master is the default variation) and query the specific field author.

18. Find Locked Nodes

Authors often lock pages and go on vacation, preventing others from editing.

type=cq:Page
path=/content
property=jcr:content/jcr:lockIsDeep
property.operation=exists
p.limit=100

Explanation: Locked nodes acquire mixin types and properties like jcr:lockIsDeep and jcr:lockOwner. We simply check if the property exists.

19. Find Expired Assets

AEM allows you to set an off-time for assets. Let's find assets that are past their expiration date.

type=dam:Asset
path=/content/dam
daterange.property=jcr:content/metadata/prism:expirationDate
daterange.upperBound=2024-01-01T00:00:00.000+00:00
p.limit=50

Explanation: We use upperBound to find assets where the expiration date is before the current date (or a specific date).

20. Complex Full-Text Search Within Specific Paths Only

Search for a term, but only within the /content/wknd/us/en branch.

fulltext=holiday
path=/content/wknd/us/en
type=cq:Page
p.limit=20

Explanation: Always combine fulltext with path. Unbounded fulltext searches across the entire /content tree are the number one cause of memory exhaustion in AEM.

21. Find Workflows in Running State

For system administrators monitoring instance health.

type=cq:Workflow
path=/var/workflow/instances
property=status
property.value=RUNNING
p.limit=-1

Explanation: Workflow instances are stored under /var/workflow/instances. Querying their status property allows you to build custom workflow dashboards.

22. Ordered by Multiple Properties

Order by modification date descending, and then by title ascending.

type=cq:Page
path=/content/wknd
1_orderby=@jcr:content/cq:lastModified
1_orderby.sort=desc
2_orderby=@jcr:content/jcr:title
2_orderby.sort=asc
p.limit=50

Explanation: Prefixing orderby with numbers allows you to establish a sorting hierarchy, just like in SQL.


Cheat Sheet

When translating these HTTP parameters to Java, use the PredicateGroup.create(map) method. Here is the direct translation matrix:

HTTP Parameter ConceptJava Map KeyExample Value
Type Restrictiontypecq:Page
Path Restrictionpath/content/we-retail
Exact Property Matchproperty, property.valuejcr:content/sling:resourceType, wknd/components/page
Property Existsproperty, property.operationjcr:content/jcr:title, exists
Full Text Searchfulltext, fulltext.relPathapple, jcr:content
Pagination Limitp.limit10, -1
Pagination Offsetp.offset20
Sortingorderby, orderby.sort@jcr:content/cq:lastModified, desc
OR conditionsgroup.p.or, group.1_property...true, ...

Java Execution Example:

Map<String, String> predicateMap = new HashMap<>();
predicateMap.put("path", "/content/wknd");
predicateMap.put("type", "cq:Page");
predicateMap.put("p.limit", "-1");

Query query = queryBuilder.createQuery(PredicateGroup.create(predicateMap), session);
SearchResult result = query.getResult();

for (Hit hit : result.getHits()) {
    // Process hit
}

Best Practices

  1. Always Use Limits: Never issue a query without p.limit. If you legitimately need all results, use p.limit=-1, but be prepared for the performance hit. In user-facing components, use p.limit=10 or similar.
  2. Path Restriction is Mandatory: Never query the root of the repository (/). Always restrict the path as deep into the tree as possible to minimize the index scope.
  3. Use the p.guessTotal Parameter: If you only need to show a pagination component and do not need the exact total count of millions of nodes, use p.guessTotal=true. This tells Oak to stop counting after a certain threshold, saving massive amounts of CPU cycles.
  4. Close Your ResourceResolvers: If your Query Builder logic utilizes a service user to obtain a Session or ResourceResolver, ensure you close it in a finally block to prevent connection leaks.

Do's & Don'ts

Do

  • DO use the Query Builder Debugger to prototype every single query before writing Java code.
  • DO check the Oak Index Manager to ensure your query is backed by a Lucene or Property index. If you see a traversal warning in your logs, your index is missing or misconfigured.
  • DO extract complex Query Builder Maps into dedicated Service classes or OSGi configurations rather than hardcoding them in Sling Models.
  • DO use @ when sorting by properties (e.g., orderby=@jcr:content/jcr:title).

Don't

  • DON'T use p.limit=-1 on large, unstructured trees like /content/dam unless running a background maintenance task.
  • DON'T use Query Builder when simple path traversal will do. If you know the exact path of a node, use ResourceResolver.getResource(path)—it is orders of magnitude faster than invoking the search engine.
  • DON'T perform full-text searches without a specific path constraint.
  • DON'T execute heavy queries directly within HTL/Sling Models synchronously during page render. Offload heavy aggregations to scheduled jobs or asynchronous endpoints.

Query Builder is arguably the most powerful API in the AEM developer's toolkit. By utilizing these recipes and strictly adhering to the performance best practices, you can build incredibly robust, scalable, and complex search functionalities across your enterprise AEM implementations.

Share this article

Discussion

By commenting you agree to the Privacy Policy. Guest comments are reviewed before they appear.

Loading discussion…

Subscribe to the Newsletter

Get the latest articles, tutorials, and tech insights delivered straight to your inbox. No spam, unsubscribe anytime.

Back to Blog