← All Posts

How to Use AEM Modernization Tools to Convert Static Templates to Editable Templates

AEM's Modernization Tools give you a structured, repeatable way to migrate pages from static templates to editable templates without rebuilding from scratch. Here's how to use them effectively — including how to handle the edge cases the default converter won't cover.

Migrating AEM pages from static templates to editable templates is one of the more tedious parts of a platform modernisation project. Done manually, it’s slow, error-prone, and hard to audit. AEM’s Modernization Tools exist specifically to automate this — but they work best when you understand what they do out of the box and where you’ll need to extend them.

This guide covers how to set up and run the tools, and how to implement a custom converter when the default behaviour doesn’t match your content structure.

What the Modernization Tools Do

At their core, the AEM Modernization Tools convert pages authored against static templates into pages that use editable templates. The conversion:

  • Updates the cq:template property on the page to point to the new editable template
  • Moves and reorganises jcr:content nodes to match the structure expected by the new template
  • Maps old component resource types to their updated equivalents
  • Creates a version of the page before any changes are applied, giving you a rollback point

Adobe provides a UI at /libs/cq/modernize/templates/content/modernize.html for running conversions, and the underlying logic is exposed as Java interfaces you can implement for custom behaviour.

Adding the Dependency

Add the modernization tools package to your project’s pom.xml:

<dependency>
  <groupId>com.adobe.cq</groupId>
  <artifactId>com.adobe.cq.modernize.tools</artifactId>
  <version>1.0.0</version>
  <scope>provided</scope>
</dependency>

Using provided scope is correct here — the package is installed in the AEM runtime and should not be bundled into your application.

Running a Basic Conversion

For straightforward migrations where the page structure maps cleanly to your editable template, the tools can be configured entirely through OSGi without writing any Java.

Your OSGi configuration needs four things:

  1. Static template path — the cq:template value on pages you want to convert
  2. Editable template path — the target template under /conf
  3. Sling resource type mappings — old resource type to new, for each component on the page
  4. Component path mappings — relative paths within jcr:content that need to be relocated

A typical configuration looks like this:

staticTemplate=/apps/mysite/templates/old-page
editableTemplate=/conf/mysite/settings/wcm/templates/new-page
slingResourceTypes=[mysite/components/old-hero:mysite/components/hero]
componentsMapping=[root/content/hero:root/container/hero]

Run the conversion through the Modernization Tools UI, select the pages you want to migrate, and the tool handles the rest.

Implementing a Custom Converter

The default converter moves all jcr:content child nodes to the root of the new page structure. This works for simple pages but breaks down when:

  • Some pages should be excluded from conversion based on their content state
  • You need to preserve specific configuration nodes (page properties, policy mappings) in their original location
  • Pages have structural inconsistencies that need to be handled selectively

For these cases, implement the PageStructureRewriteRule interface.

The Interface

public interface PageStructureRewriteRule {
    String getStaticTemplate();
    boolean matches(Node pageContent) throws RepositoryException;
    Node applyTo(Node pageContent, Set<String> finalNodes) throws RewriteException, RepositoryException;
}

getStaticTemplate()

Returns the path of the static template this rule applies to. The conversion engine uses this to filter which rules are evaluated for a given page:

@Override
public String getStaticTemplate() {
    return config.staticTemplate();
}

matches()

Called before conversion to determine whether this page should be processed. This is where you implement exclusion logic — checking for the presence or absence of specific nodes, property values, or content states:

@Override
public boolean matches(Node pageContent) throws RepositoryException {
    if (!pageContent.hasProperty("cq:template")) {
        return false;
    }
    String template = pageContent.getProperty("cq:template").getString();
    return config.staticTemplate().equals(template);
}

If matches() returns false, the page is skipped without error. Use this to exclude pages that are structurally incomplete or that have already been partially migrated.

applyTo()

This is where the conversion happens. The method receives the jcr:content node of the page and a set of node names that should be left in place (finalNodes). Return the modified node when done:

@Override
public Node applyTo(Node pageContent, Set<String> finalNodes) throws RewriteException, RepositoryException {
    Session session = pageContent.getSession();

    // Create a version before modifying
    VersionManager vm = session.getWorkspace().getVersionManager();
    vm.checkpoint(pageContent.getParent().getPath());

    // Update the template reference
    pageContent.setProperty("cq:template", config.editableTemplate());

    // Remove the design path — not used by editable templates
    if (pageContent.hasProperty("cq:designPath")) {
        pageContent.getProperty("cq:designPath").remove();
    }

    // Move components to their new locations
    // Add your node relocation logic here based on your template structure

    session.save();
    return pageContent;
}

The finalNodes set tells you which nodes the engine has already decided to keep at root level. Respect these — don’t relocate nodes that appear in this set, or you’ll create conflicts with the template structure.

Handling Node Order

Editable templates define a specific node order under jcr:content. After moving nodes, you may need to reorder them to match. Use orderBefore() on the parent node:

pageContent.orderBefore("header", "root");
pageContent.orderBefore("root", "footer");

Get the expected order from your editable template’s structure under /conf/mysite/settings/wcm/templates/new-page/structure/jcr:content.

What to Test Before Running at Scale

Before running the converter across a large content tree:

  1. Run on a single page first and inspect the result in CRXDE. Confirm node positions, resource types, and template references are correct.
  2. Verify the version was created on the page before conversion — this is your only rollback path without a full repository restore.
  3. Check page properties still render correctly. If your page properties dialog relies on nodes that were relocated during conversion, the authoring UI may break even if the published page looks fine.
  4. Test in a non-production environment with a representative sample of pages, including any structural outliers your matches() logic is designed to handle.

The conversion is non-destructive by design — versions are created, nothing is deleted — but a bad node order or missing resource type mapping will require manual correction at scale, which defeats the purpose of automating the migration in the first place.