AEM Experience Fragments: URL Externalization with Adobe TargetAEM Experience Fragments: URL Externalization with Adobe Target

Tech Tips

6 min read

Arrow hitting the bullseye of a circular target labeled URL on a dark blue background.

Tags

#AEM

#Digital Marketing Technology

#Experience Fragments

Share

In most cases, internal links in HTML are relative links, but there may be cases when custom components provide full URLs in HTML. By default, such URLs are ignored. In this article we will show how to deal with this type of link so that they are sent into target in the correct format.

The Basics

Using the “Export to Target” feature you can create an XF, add components to it, and then export it as an Adobe Target Offer. This feature can be enabled on an author instance of AEM. Since Experience Fragments are exported from the AEM author instance, you need to make sure that any references within the XF are externalized for web delivery. This requires a valid Adobe Target Configuration and configurations for the link externalizer. The Link Externalizer is used to determine the URLs for the HTML version of the Target Offer. Adobe Target requires all links inside the Target HTML Offer to be publicly accessed, so you need to publish the XF itself and all its links before pushing to Target. When a Target HTML Offer is being constructed, a request is sent to a custom Sling selector in AEM. This selector is called .atoffer.nocloudconfigs.html. After you generate the HTML page, the Sling Rewriter pipeline makes modifications to the output:

  • The HTML, head, and body elements are replaced with div elements. The meta, noscript, and title elements are removed.
  • AEM modifies any internal links present in HTML so that they point to a published resource. AEM follows this pattern for attributes of HTML elements:
    • src attributes
    • href attributes
    • *-src attributes (like data-src, custom-src, etc.)
    • *-href attributes (like data-href, custom-href, img-href, etc.)

Experience Fragment Link Rewriter Provider

This is an extension point offered to clients in order to allow link rewriting for the Export to Target feature. Services implementing this interface will be called during the Export to Target offer content generation for HTML.

When an Export to Target call is made in the AEM Experience Fragment console, the body of the HTML content is generated through a request to an XF with the selector atoffer.html. Before this HTML content is sent to Target, we would need to rewrite all the links so that they can be accessed publicly. We already rewrote these links using Externalizer, but if you need more customization, a service can implement this interface to make that possible.

Currently, only one ExperienceFragmentLinkRewriterProvider for an XF is permitted. If there are multiple providers for an XF, only the one with the highest priority is chosen.

Priority is determined by the providers getPriority() method. If there are several ExperienceFragmentLinkRewriterProvider with the same priority, then only one will be chosen.

Example

A new rewrite config /Site-com/ui.apps/src/main/content/jcr_root/apps/site-com/config/rewriter/xf-target/.content.xml

<!--?xml version="1.0" encoding="UTF-8"?-->
<jcr:root xmlns:jcr="http://www.jcp.org/jcr/1.0" xmlns:nt="http://www.jcp.org/jcr/nt/1.0" jcr:primarytype="nt:unstructured" contenttypes="[text/html]" enabled="{Boolean}true" generatortype="htmlparser" order="6000" serializertype="htmlwriter" selectors="[atoffer]" extensions="[html]" transformertypes="[linkchecker,mobile,mobiledebug,contentsync,versioned-clientlibs,adobe-target-offer]">
   <transformer-mobile jcr:primarytype="nt:unstructured" component-optional="{Boolean}true">
   <transformer-mobiledebug jcr:primarytype="nt:unstructured" component-optional="{Boolean}true">
   <transformer-contentsync jcr:primarytype="nt:unstructured" component-optional="{Boolean}true">
   <generator-htmlparser jcr:primarytype="nt:unstructured" includetags="[A,/A,IMG,AREA,FORM,BASE,LINK,SCRIPT,BODY,/BODY]">
</generator-htmlparser></transformer-contentsync></transformer-mobiledebug></transformer-mobile></jcr:root>
  • Make sure “atoffer” selector is added
  • Make sure “adobe-target-offer” transformer type is added
  • Make sure order parameter is higher than the order of your default rewrite config

/site-com/core/src/main/java/com/wcm/site/xf/XFLinkRewriter.java:

package com.wcm.site.xf;
 
import com.adobe.cq.xf.ExperienceFragmentLinkRewriterProvider;
import com.adobe.cq.xf.ExperienceFragmentVariation;
import com.adobe.cq.xf.ExperienceFragmentsConstants;
import com.wcm.site.model.LinkUpdateParams;
import com.wcm.site.services.LinkExternalizeService;
import com.wcm.site.util.LinkUtils;
import static com.wcm.site.util.LinkUtils.joinAsPath;
import com.wcm.site.util.ServiceUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.api.resource.ResourceResolverFactory;
import org.apache.sling.serviceusermapping.ServiceUserMapped;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.component.propertytypes.ServiceDescription;
import org.osgi.service.metatype.annotations.Designate;
import org.osgi.service.metatype.annotations.ObjectClassDefinition;
 
 
@Component(immediate = true, service = ExperienceFragmentLinkRewriterProvider.class)
@ServiceDescription("XF to Target link rewriter")
@Designate(ocd = XFLinkRewriter.XFLinkRewriterConfig.class)
public class XFLinkRewriter implements ExperienceFragmentLinkRewriterProvider {
 
    @ObjectClassDefinition(name = "XF Link Rewriter Config")
    public @interface XFLinkRewriterConfig {
    }
 
    @Reference
    private LinkExternalizeService linkExternalizeService;
 
    @Reference
    private ResourceResolverFactory resourceResolverFactory;
 
 
    @Override
    public String rewriteLink(String link, String tag, String attribute) {
        if (StringUtils.isBlank(link)) {
            return link;
        }
 
        try (ResourceResolver resourceResolver = ServiceUtils.createResourceResolver(resourceResolverFactory)){
            if(resourceResolver == null) {
                return link;
            }
            String path = getPath(resourceResolver, link);
            if(LinkUtils.isExternal(path)) {
                return path;
            }
 
            return linkExternalizeService.publishLink(resourceResolver, path);
        }
    }
 
    @Override
    public boolean shouldRewrite(ExperienceFragmentVariation experienceFragmentVariation) {
        return StringUtils.startsWith(experienceFragmentVariation.getPath(), “/content/experience-fragments/to-target”);
    }
 
    @Override
    public int getPriority() {
        return 0;
    }
 
    private String getPath(ResourceResolver resourceResolver, String url) {
        String authorDomain = linkExternalizeService.authorLink(resourceResolver, StringUtils.EMPTY);
        return StringUtils.removeStart(StringUtils.removeStart(url, authorDomain), LinkUtils.removeScheme(authorDomain));
    }
 
}

In this example we show how we rewrote author links with the relative ones. You may face your own unique issues, but this basic approach will be useful to correct the target data.

Author: Iryna Ason

Resource Hub

Our Latest Stories & Industry Insights

View Resource Hub

Women in Exadel: Let's Talk — Stories of Growth, Leadership, and Resilience

7 min read

July 2, 2026

AEM Cloud Migration using Agentic AI: Beyond Generic Tooling

7 min read

June 30, 2026

AI Readiness Assessment for Financial Services: Benchmarks, Challenges, and How to Move Forward

15 min read

June 29, 2026

How to Run an AI Readiness Assessment: A Step-by-Step Framework

14 min read

June 26, 2026

The 5 Stages of AI Readiness: Where Does Your Organization Stand?

9 min read

June 12, 2026

Whitepaper: Token Cost Optimization for Enterprise AI. A Practical Framework to Reduce LLM Costs by 50–80%

28 min read

June 9, 2026
Two people sitting at a table with a laptop.

Let’s make your next project faster, safer, smarter.

Get In Touch
document.querySelectorAll('a[href="/contact"]').forEach(function(link) { link.setAttribute('href', '/contact/'); });