<

AEM Expanded Page Index Bundle

A standalone AEM bundle that extracts content from cq:Page nodes — including referenced Content Fragments — and applies the cq:Searchable mixin with search properties, enabling fulltext and semantic search via the cqSiteSearch Oak Elasticsearch index.

Search properties are written directly to the original /content nodes. A lightweight mirror at /var/indexing/expanded-pages is maintained for operational tracking (dirty flags, Content Fragment backreferences).

Quickstart

1. Add the Maven repository to all/pom.xml

<repositories>
    <repository>
        <id>oak-indexing</id>
        <url>https://oak-indexing.github.io/maven-repository/</url>
        <snapshots><enabled>true</enabled></snapshots>
        <releases><enabled>true</enabled></releases>
    </repository>
</repositories>

2. Embed the bundle in all/pom.xml

In the <embeddeds> block of filevault-package-maven-plugin:

<embedded>
    <groupId>com.adobe.aem.indexing</groupId>
    <artifactId>expanded-page-index</artifactId>
    <version>1.5.6</version>
    <target>/apps/your-packages/application/install</target>
</embedded>

3. Add OSGi configs to your ui.config module

The bundle embeds its own repoinit and service user mapping via Sling-Initial-Content, but in AEM Cloud Service there is a known ordering risk where the bundle can activate before the JCR Installer feeds those configs to ConfigAdmin. To guarantee reliable startup, add these two files to your project's ui.config module:

ui.config/.../osgiconfig/config/org.apache.sling.jcr.repoinit.RepositoryInitializer~expanded-page-index.cfg.json

{
  "scripts": [
    "create path (sling:Folder) /var/indexing/expanded-pages/content\n\ncreate service user expanded-page-index-service with forced path system/cq:services/expanded-page-index\n\nset principal ACL for expanded-page-index-service\n  allow jcr:read,jcr:modifyProperties,jcr:nodeTypeManagement on /content\n  allow jcr:all on /var\n  allow jcr:read on /oak:index\nend"
  ]
}

ui.config/.../osgiconfig/config/org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.amended~expanded-page-index.cfg.json

{
  "user.mapping": [
    "com.adobe.aem.indexing.expand:expanded-page-index=[expanded-page-index-service]"
  ]
}

4. Deploy the Oak indexes

The bundle requires two Oak indexes before it activates. These are deployed by Adobe into your AEM Cloud Service environment:

The bundle is a strict NOP until these two indexes are present — it is safe to deploy the bundle ahead of time and wait for index deployment. The cqSiteSearch-1-custom-1 Elasticsearch index is required for search queries to work, but is not a condition for the bundle to activate.

Search Properties

The bundle writes the following properties to each cq:Page node under /content:

PropertyDescription
cq:searchTitlePage title
cq:searchDescriptionPage description
cq:searchContentExtracted page body (multi-value, HTML stripped)

Search query

SELECT [cq:searchTitle], [cq:searchDescription], [jcr:path]
FROM [cq:Searchable]
WHERE ISDESCENDANTNODE('/content/mysite/us/en')
AND contains(*, 'search terms')
OPTION (INDEX TAG cqSiteSearch)

Configuration

The scheduler runs every 30 seconds by default. To change the interval, configure OSGi PID com.adobe.aem.indexing.expand.scheduler.ExpandedPageIndexScheduler:

PropertyDefaultDescription
scheduler.expression0/30 * * * * ?Cron expression
scheduler.concurrentfalsePrevent overlapping runs

The bundle itself is configured via OSGi PID com.adobe.aem.indexing.expand.config.ExpandedPageIndexConfig:

PropertyDefaultDescription
referencePropertyPattern fragmentPath Regex (full match) applied to property names on cq:Page subnodes to identify Content Fragment references. Adjust when your components use a non-standard property name (e.g. cfArticleReference, cf.*Reference).
contentPropertiesPropertyName elementNames Name of the sibling property that lists which fragment fields to extract (source-driven / WKND style). Set to empty to disable this strategy.
contentPropertyPattern (empty) Regex (full match) applied to property names inside the fragment's jcr:content/data/master node. All matching properties are extracted as body text (pattern-driven / Telekom style). Leave empty to disable.

Content Fragment extraction strategies

Two strategies control how text is pulled out of referenced Content Fragments. They run independently and can be used simultaneously.

Example config using both strategies together:

{
  "referencePropertyPattern": "cf.*Reference",
  "contentPropertiesPropertyName": "elementNames",
  "contentPropertyPattern": "body|text|description"
}

Mirror Structure

The mirror at /var/indexing/expanded-pages holds operational state only — dirty flags and CF backreferences. It is not used for search.

/var/indexing/expanded-pages/
├── state/
│   └── lastRun              (timestamp of last successful extraction run)
└── content/                 (mirrors /content)
    └── mysite/
        └── us/
            └── en/
                └── page/    (cq:PageMirror)
                    ├── path                 = "/content/mysite/us/en/page"
                    ├── dirty                = false
                    └── fragmentReferences   = ["/content/dam/fragments/..."]

Management

The bundle exposes a JMX MBean for operational tasks.
Navigate to: http://localhost:4502/system/console/jmxcom.adobe.aem.indexing.expandExpandedPageIndexService

OperationDescription
runExtraction()Process pages changed since the last run
rebuildMirror()Delete the mirror and rebuild from scratch
clearMirror()Delete the mirror without rebuilding

Troubleshooting

Bundle does not start — extractionService unsatisfied

The ExpandedPageIndexScheduler depends on ExpandedPageIndexService. If the service component is unsatisfied, check it directly in the OSGi console and look for any unsatisfied references there.

Bundle starts but logs "Cannot derive user name"

The service user mapping was not processed before the bundle activated. Add the ServiceUserMapperImpl.amended config to your ui.config module as described in Quickstart step 3.

Bundle starts but mirror is empty

Cause: Oak indexes not yet deployed.
Check: Browse to /oak:index/cqPageMirror-1-custom-1 in CRXDE. If missing, the indexes have not been deployed yet. The bundle logs "Oak indexes not yet available" each scheduler cycle until they exist.

Bundle starts, indexes exist, but extraction never runs

Look for "Content extraction service activated" in error.log. If absent, the service user expanded-page-index-service may not have been created by the bundle's repoinit script. Check for repoinit errors at startup.

Elasticsearch index is empty

  1. Confirm the cq:Searchable mixin is present on target nodes (CRXDE — check a /content page node)
  2. Confirm the elastic-async lane is running — check IndexStats for elastic-async in the JMX console
  3. Confirm cqSiteSearch-1-custom-1 exists at /oak:index/cqSiteSearch-1-custom-1 and has type=elasticsearch

Searches return no results

  1. Confirm the Elasticsearch index contains documents (check via the Oak Query Diagnostics or ES API)
  2. Confirm OPTION (INDEX TAG cqSiteSearch) is present in your query

Pages not updating after content changes

Mirror Mode (advanced)

By default, cq:Searchable properties are written to the original /content nodes (ORIGINAL mode). An alternative MIRROR mode writes them to /var/indexing/expanded-pages instead, keeping /content untouched. To enable it, add an OSGi config to your ui.config:

ui.config/.../osgiconfig/config/com.adobe.aem.indexing.expand.config.ExpandedPageIndexConfig.cfg.json

{
  "searchMode": "MIRROR"
}

In MIRROR mode, adjust your search query to target /var/indexing/expanded-pages instead of /content.

Help

If you have any issues or questions, please open a ticket.