All groovy scripts notes
Groovy Console Snippets#
#Groovy Console Snippets for AEM Development
All scripts below are read-only — no mutations to the repository. Run via AEM Groovy Console (requires
ACS AEM Commonsor standaloneAEM Groovy Consolebundle).
#Find Pages Containing a Component
#By resource type — which pages use a component?
// ✅ USE CASE: "Where is /apps/mysite/components/hero used?"
def componentResourceType = "mysite/components/hero"
def query = """
SELECT * FROM [nt:base] AS s
WHERE ISDESCENDANTNODE(s, '/content')
AND [sling:resourceType] = '$componentResourceType'
"""
def session = resourceResolver.adaptTo(Session)
def q = session.workspace.queryManager.createQuery(query, "JCR-SQL2")
def results = q.execute()
def pages = [] as Set
results.nodes.each { node ->
def current = node
while (current != null && !current.isNodeType("cq:Page")) {
current = current.parent
}
if (current) pages << current.path
}
println "=== Pages using: $componentResourceType ==="
println "Total pages: ${pages.size()}\n"
pages.sort().each { println " - $it" }#Scoped search — component usage under a specific content root
def componentResourceType = "mysite/components/teaser"
def searchRoot = "/content/mysite"
def query = """
SELECT * FROM [nt:base] AS s
WHERE ISDESCENDANTNODE(s, '$searchRoot')
AND [sling:resourceType] = '$componentResourceType'
"""
def session = resourceResolver.adaptTo(Session)
def q = session.workspace.queryManager.createQuery(query, "JCR-SQL2")
def results = q.execute()
println "=== Component nodes: $componentResourceType under $searchRoot ===\n"
results.nodes.each { println " ${it.path}" }#Page Inspection
#Get page properties and template of a page
// ✅ USE CASE: Inspect page metadata, template, title, description
def pagePath = "/content/mysite/en/home"
def pageManager = resourceResolver.adaptTo(com.day.cq.wcm.api.PageManager)
def page = pageManager.getPage(pagePath)
if (!page) { println "❌ Page not found: $pagePath"; return }
def props = page.properties
println "=== Page: $pagePath ==="
println "Title : ${page.title}"
println "Template : ${props.get('cq:template', 'N/A')}"
println "Last Modified: ${props.get('cq:lastModified', 'N/A')}"
println "Last Mod By : ${props.get('cq:lastModifiedBy', 'N/A')}"
println "Created : ${props.get('jcr:created', 'N/A')}"
println "Nav Title : ${props.get('navTitle', 'N/A')}"
println "Description : ${props.get('jcr:description', 'N/A')}"
println "Tags : ${props.get('cq:tags', [])}"
println "On-time : ${props.get('onTime', 'N/A')}"
println "Off-time : ${props.get('offTime', 'N/A')}"#Traverse all child pages of a root
// ✅ USE CASE: Audit all pages in a site tree
def rootPath = "/content/mysite/en"
def maxDepth = 3
def count = 0
def traverse
traverse = { page, depth ->
if (depth > maxDepth) return
def indent = " " * depth
println "${indent}- [${page.name}] ${page.path} | Template: ${page.properties.get('cq:template','N/A')}"
count++
page.listChildren().each { child -> traverse(child, depth + 1) }
}
def pageManager = resourceResolver.adaptTo(com.day.cq.wcm.api.PageManager)
def root = pageManager.getPage(rootPath)
if (root) traverse(root, 0)
println "\nTotal pages: $count"#Template Audit
#Find all pages using a specific editable template
// ✅ USE CASE: Audit pages before modifying/deleting a template
def templatePath = "/conf/mysite/settings/wcm/templates/content-page"
def query = """
SELECT * FROM [cq:Page] AS page
WHERE ISDESCENDANTNODE(page, '/content')
AND page.[jcr:content/cq:template] = '$templatePath'
"""
def session = resourceResolver.adaptTo(Session)
def q = session.workspace.queryManager.createQuery(query, "JCR-SQL2")
def results = q.execute()
println "=== Pages using template: $templatePath ===\n"
def count = 0
results.nodes.each { println " ${it.path}"; count++ }
println "\nTotal: $count pages"#List all editable templates in /conf
// ✅ USE CASE: Discover available templates per site config
def query = """
SELECT * FROM [cq:Template] AS t
WHERE ISDESCENDANTNODE(t, '/conf')
"""
def session = resourceResolver.adaptTo(Session)
def q = session.workspace.queryManager.createQuery(query, "JCR-SQL2")
def results = q.execute()
println "=== Editable Templates under /conf ===\n"
results.nodes.each { node ->
def title = node.hasProperty("jcr:title") ? node.getProperty("jcr:title").string : "(no title)"
println " [${title}] -> ${node.path}"
}#JCR Node Inspection
#Read all properties of a specific JCR node
// ✅ USE CASE: Debug what a component node stores
def nodePath = "/content/mysite/en/home/jcr:content/root/container/hero"
def resource = resourceResolver.getResource(nodePath)
if (!resource) { println "❌ Node not found: $nodePath"; return }
println "=== Properties at: $nodePath ===\n"
resource.valueMap.entrySet().sort { it.key }.each { entry ->
println " ${entry.key} = ${entry.value}"
}#Find nodes with a specific property value
// ✅ USE CASE: Find all nodes where linkURL = '/content/mysite/en/about'
def searchRoot = "/content/mysite"
def propertyName = "linkURL"
def propertyValue = "/content/mysite/en/about"
def query = """
SELECT * FROM [nt:base] AS s
WHERE ISDESCENDANTNODE(s, '$searchRoot')
AND [$propertyName] = '$propertyValue'
"""
def session = resourceResolver.adaptTo(Session)
def q = session.workspace.queryManager.createQuery(query, "JCR-SQL2")
def results = q.execute()
println "=== Nodes with [$propertyName] = '$propertyValue' ===\n"
results.nodes.each { println " ${it.path}" }#Find nodes where a property contains a substring (LIKE)
// ✅ USE CASE: Find all components referencing a DAM folder path
def searchRoot = "/content/mysite"
def propertyName = "fileReference"
def substring = "/content/dam/mysite/banners"
def query = """
SELECT * FROM [nt:base] AS s
WHERE ISDESCENDANTNODE(s, '$searchRoot')
AND [$propertyName] LIKE '$substring%'
"""
def session = resourceResolver.adaptTo(Session)
def q = session.workspace.queryManager.createQuery(query, "JCR-SQL2")
def results = q.execute()
println "=== Nodes with [$propertyName] LIKE '$substring%' ===\n"
results.nodes.each { node ->
def val = node.hasProperty(propertyName) ? node.getProperty(propertyName).string : "?"
println " ${node.path} => $val"
}#Component Deep Audit
#Dump all component instances on a single page with their params
// ✅ USE CASE: Full component property dump for debugging authoring content
def pagePath = "/content/mysite/en/home/jcr:content"
def root = resourceResolver.getResource(pagePath)
if (!root) { println "❌ Not found: $pagePath"; return }
def inspect
inspect = { resource, depth ->
def indent = " " * depth
def rt = resource.valueMap.get("sling:resourceType", "")
if (rt) {
println "${indent}[${resource.name}] sling:resourceType = $rt"
resource.valueMap.entrySet()
.findAll { !it.key.startsWith("jcr:") && it.key != "sling:resourceType" }
.sort { it.key }
.each { println "${indent} ${it.key} = ${it.value}" }
}
resource.children.each { child -> inspect(child, depth + 1) }
}
inspect(root, 0)#Count component usage across entire site (inventory report)
// ✅ USE CASE: How many times is each component used site-wide?
def searchRoot = "/content/mysite"
def query = """
SELECT * FROM [nt:base] AS s
WHERE ISDESCENDANTNODE(s, '$searchRoot')
AND [sling:resourceType] IS NOT NULL
"""
def session = resourceResolver.adaptTo(Session)
def q = session.workspace.queryManager.createQuery(query, "JCR-SQL2")
q.limit = 50000
def results = q.execute()
def counts = [:].withDefault { 0 }
results.nodes.each { node ->
if (node.hasProperty("sling:resourceType")) {
counts[node.getProperty("sling:resourceType").string]++
}
}
println "=== Component Usage Report under $searchRoot ===\n"
counts.sort { -it.value }.each { rt, c -> println " ${String.format('%4d', c)}x $rt" }
println "\nDistinct components: ${counts.size()}"#Tag & Metadata Audit
#Find all pages tagged with a specific tag
// ✅ USE CASE: Find content tagged with a business category
def targetTag = "mysite:topic/digital-transformation"
def searchRoot = "/content/mysite"
def query = """
SELECT * FROM [cq:Page] AS page
WHERE ISDESCENDANTNODE(page, '$searchRoot')
AND page.[jcr:content/cq:tags] = '$targetTag'
"""
def session = resourceResolver.adaptTo(Session)
def q = session.workspace.queryManager.createQuery(query, "JCR-SQL2")
def results = q.execute()
println "=== Pages tagged [$targetTag] ===\n"
results.nodes.each { println " ${it.path}" }#Audit pages missing SEO metadata (title or description)
// ✅ USE CASE: Detect SEO gaps before a site audit
def searchRoot = "/content/mysite/en"
def pageManager = resourceResolver.adaptTo(com.day.cq.wcm.api.PageManager)
def root = pageManager.getPage(searchRoot)
def issues = []
def traverse
traverse = { page ->
def props = page.properties
def hasTitle = props.get("jcr:title", "").trim() as boolean
def hasDesc = props.get("jcr:description", "").trim() as boolean
if (!hasTitle || !hasDesc) {
def flags = []
if (!hasTitle) flags << "NO TITLE"
if (!hasDesc) flags << "NO DESCRIPTION"
issues << "${page.path} [${flags.join(', ')}]"
}
page.listChildren().each { traverse(it) }
}
if (root) traverse(root)
println "=== Pages missing SEO metadata ===\n"
issues.each { println " $it" }
println "\nTotal issues: ${issues.size()}"#Reference Finder
#Find all pages that link to a specific page path
// ✅ USE CASE: Before deleting /content/mysite/en/about-us, who links to it?
def targetPath = "/content/mysite/en/about-us"
def searchRoot = "/content/mysite"
def query = """
SELECT * FROM [nt:base] AS s
WHERE ISDESCENDANTNODE(s, '$searchRoot')
AND (
[linkURL] = '$targetPath'
OR [href] = '$targetPath'
OR [pageLink] = '$targetPath'
OR [ctaLink] = '$targetPath'
)
"""
def session = resourceResolver.adaptTo(Session)
def q = session.workspace.queryManager.createQuery(query, "JCR-SQL2")
def results = q.execute()
def pages = [] as Set
results.nodes.each { node ->
def current = node
while (current != null && !current.isNodeType("cq:Page")) {
current = current.parent
}
if (current) pages << current.path
}
println "=== Pages referencing: $targetPath ===\n"
pages.sort().each { println " $it" }
println "\nTotal: ${pages.size()} pages"#OSGi & Run Mode Context
#Print current run modes
// ✅ USE CASE: Confirm environment (author/publish/dev/stage/prod)
import org.apache.sling.settings.SlingSettingsService
def settings = sling.getService(SlingSettingsService)
println "=== Sling Run Modes ==="
settings.runModes.sort().each { println " - $it" }#Replication Status
#Find pages that have never been activated
// ✅ USE CASE: Find draft pages that were never published
def searchRoot = "/content/mysite/en"
def query = """
SELECT * FROM [cq:Page] AS page
WHERE ISDESCENDANTNODE(page, '$searchRoot')
"""
def session = resourceResolver.adaptTo(Session)
def q = session.workspace.queryManager.createQuery(query, "JCR-SQL2")
def results = q.execute()
println "=== Pages never activated under $searchRoot ===\n"
def count = 0
results.nodes.each { node ->
def jcrContent = node.hasNode("jcr:content") ? node.getNode("jcr:content") : null
if (jcrContent && !jcrContent.hasProperty("cq:lastReplicationAction")) {
println " ${node.path}"
count++
}
}
println "\nNever activated: $count pages"#Tips
| Tip | Detail |
|---|---|
| Limit results | Use q.limit = N for large sites to prevent OOM errors |
| Session | Session = javax.jcr.Session, auto-available via resourceResolver.adaptTo(Session) |
resourceResolver | Pre-bound variable in the Groovy console — no setup needed |
pageManager | resourceResolver.adaptTo(com.day.cq.wcm.api.PageManager) |
| Read-only safe | None of these snippets call session.save() — safe to run anywhere |
| Oak performance | Prefer JCR-SQL2 with ISDESCENDANTNODE over XPath for Oak queries |
| Result cap | Oak default query limit is 10,000 — always set q.limit explicitly |