User guide
What this tool does
The AEM Query Builder lets you build a QueryBuilder query in a form instead of typing predicates by hand. As you fill it in, it writes the same query four ways: predicate text for the Query Debugger, a /bin/querybuilder.json URL, Java code using the QueryBuilder API, and an approximate JCR-SQL2 version. It also checks the query for common mistakes and slow patterns.
It works for both AEM as a Cloud Service and AEM 6.5. The version switch changes the index and debugging advice.
When to use it
- Finding content during development or support: every page using a component, every asset of one type, fragments of one model.
- Writing a query for code: get the predicates right in the form, then copy the Java version into a Sling Model or servlet.
- Debugging a slow or empty query: paste an existing query from your logs or code and read the checks.
- Content audits: pages not modified in a year, pages missing a tag, pages with a given template.
- Moving a query between forms: turn a querybuilder.json URL from a colleague into Java, or into JCR-SQL2 for a custom index test.
How to use it
- Pick your AEM version at the top: AEM as a Cloud Service or AEM 6.5.
- Start from a preset if one is close to what you need, or fill in the form from scratch.
- Set where to search. Always give a path and a node type: they decide which index Oak can use. Use
cq:Pagefor pages,dam:Assetfor assets and fragments, andnt:unstructuredfor component nodes. - Add property conditions. Each one needs a property path relative to the node type, a condition and one or more values. Several values match any of them unless you tick All values must match. All the conditions in this section must match together.
- Add dates, tags or full-text if you need them. Relative dates such as
-7dmove with the clock; fixed dates do not. - Use an OR group when you need “either this or that”: at least one condition inside the group must match.
- Set sorting and results. QueryBuilder returns only 10 results unless you set
p.limit. Use-1for all results, together withp.guessTotal. - Copy the output you need from the tabs on the right, and read the Checks tab.
- Run it against your AEM. Enter your author URL once, for example
http://localhost:4502, then use Open in Query Debugger or Run as JSON. The links open in a new tab and use the AEM login you already have in that browser.
Presets
The Start from a preset menu fills in a complete query you can adjust. After loading, a note above the form says what to change, usually the site path or an example value.
| Preset | Finds |
|---|---|
| Page finder | Pages whose title contains a word (case-sensitive) |
| Pages modified in the last 7 days | Recently edited pages, newest first |
| Template usage | Every page built from one editable template |
| Unpublished pages | Pages never published, or whose last action was Unpublish |
| Stale pages: edited since last publish | Published pages with edits that are not live yet, using dateComparison |
| Stale pages: not updated in 12 months | Old content that may need a review or removal |
| Missing SEO metadata | Pages without a meta description (jcr:description) |
| Pages with a tag | Pages carrying either of two tags |
| Where a component is used | Every node using a component’s resource type |
| Full-text search in pages | Pages whose content mentions a word |
| Assets by MIME type | Images or documents of given types |
| Assets missing metadata | Assets without a title or description |
| Duplicate assets | Assets with the same file, matched on dam:sha1 |
| Similar assets | Assets whose text and metadata resemble one asset, using similar |
| Content fragments of one model | All fragments built from a given model |
To work on an existing query, press Import query and paste predicate lines, a /bin/querybuilder.json URL or a Query Debugger URL.
Reading the results
| Output | What it is | Where to use it |
|---|---|---|
| Predicates | key=value lines, the native QueryBuilder format |
Paste into the Query Debugger at /libs/cq/search/content/querydebug.html |
| JSON URL | The same query as a GET request | Open in a browser where you are logged in to AEM, or call from scripts |
| Java | A Map of predicates passed to QueryBuilder.createQuery |
OSGi services, Sling Models and servlets |
| JCR-SQL2 | An approximate translation | Explain Query, CRXDE’s query tool, QueryManager in code |
The checks come in three levels:
- Problems (✕) are mistakes that will give wrong or no results, such as querying
cq:templateon acq:Pageinstead ofjcr:content/cq:template. - Warnings (!) are likely performance issues, such as no path, no node type, or fetching every result without
p.guessTotal. - Notes (i) are advice, such as making sure a sorted property is indexed with
ordered=true.
The JCR-SQL2 tab is a translation, not an exact copy. QueryBuilder has evaluators that SQL2 lacks, and relative dates become fixed dates at the moment the query is generated. Always confirm with Explain Query on your instance.
Worked example
You want the 20 most recently changed pages under /content/mysite from the last week. Choose the preset Pages modified in the last 7 days. The predicates are:
path=/content/mysite
type=cq:Page
relativedaterange.property=jcr:content/cq:lastModified
relativedaterange.lowerBound=-7d
orderby=@jcr:content/cq:lastModified
orderby.sort=desc
p.limit=20
On a local author, the JSON URL becomes:
http://localhost:4502/bin/querybuilder.json?path=/content/mysite&type=cq:Page&relativedaterange.property=jcr:content/cq:lastModified&relativedaterange.lowerBound=-7d&orderby=@jcr:content/cq:lastModified&orderby.sort=desc&p.limit=20
And the JCR-SQL2 translation, generated on 1 November 2026:
SELECT s.* FROM [cq:Page] AS s
WHERE ISDESCENDANTNODE(s, '/content/mysite')
AND s.[jcr:content/cq:lastModified] >= CAST('2026-10-25T00:00:00.000Z' AS DATE)
ORDER BY s.[jcr:content/cq:lastModified] DESC
Notice that the date sits on jcr:content, not on the page node itself. That is the most common reason page queries come back empty.
Limits and privacy
- Everything runs in your browser. The tool never contacts your AEM; the Open links simply open a new tab, and your browser sends the request with your own login.
- Your query and author URL are saved in this browser (local storage), so a refresh keeps them. Nothing is sent to this site.
- Share links contain the query itself. Do not share links that reveal internal paths you would rather keep private.
- Supported predicates: path, type, nodename, property (all operations), daterange, relativedaterange, dateComparison, similar, tagid, fulltext, OR groups, orderby and the
p.options. Other predicates, such aslanguageorhasPermission, are listed as left out when you import a query. - Two predicates work differently from the rest.
dateComparisoncannot use an index: it checks each result after the query runs, so keep its path narrow.similarranks results by text and metadata through the full-text index, not by how images look. - Duplicates are found one file at a time. QueryBuilder cannot group results, so the duplicate preset finds every copy of one file you choose by its
dam:sha1. To stop new duplicates, turn on AEM Assets’ duplicate detection at upload; for a whole-DAM report, use a script. - Block QueryBuilder on publish.
/bin/querybuilder.jsonshould be denied by your Dispatcher filters on publish instances. Use it on author, or behind authentication.
Further reading
- Query Builder API, Adobe Experience League
- Query Builder predicate reference, Adobe Experience League
- Content search and indexing, AEM as a Cloud Service documentation
- Oak query documentation, Apache Jackrabbit Oak
FAQ
- Does this tool connect to my AEM instance?
- No. It builds the query in your browser. The “Open” links open a new tab on your own AEM author, using the login you already have there.
- Why does my page query return nothing?
- Most often the property is on the wrong node. Page properties such as cq:template, cq:tags and cq:lastModified live on jcr:content, so use jcr:content/cq:template, not cq:template. The Checks tab flags this.
- Why do I only get 10 results?
- QueryBuilder returns 10 hits unless you set p.limit. Use p.limit=-1 for all results, with p.guessTotal=true so AEM does not count every match first.
- Is the JCR-SQL2 output exactly the same query?
- It is a close translation for the predicates this tool supports. QueryBuilder adds its own evaluators, so confirm the plan with Explain Query on your instance.
- Does it work with AEM as a Cloud Service and AEM 6.5?
- Yes. The QueryBuilder API is the same on both. The version switch changes the advice about indexes and where to find Explain Query.