Expand my Community achievements bar.

Submissions are now open for the 2026 Adobe Experience Maker Awards.
SOLVED

Customising Teaser V2 Component to Support External URLs (like v1)

Avatar

Level 2

Hi everyone,

We originally worked with Teaser v1 for a while now, and only upgraded to v2 to included smart cropping image features. Now I need to extend the Teaser component to support external URLs in the linkURL field, similar to how v1 handled it. 

In v2, the getTargetPage() method in TeaserImpl is stricter. It attempts to resolve the linkURL to a Page using PageManager, and if it can't, it falls back to the current page or an action CTA. Here's the current implementation in core AEM:

@Override
    @notnull
    protected Optional<Page> getTargetPage() {
        if (this.targetPage == null) {
            String linkURL = resource.getValueMap().get(ImageResource.PN_LINK_URL, String.class);
            if (StringUtils.isNotEmpty(linkURL)) {
                this.targetPage = Optional.ofNullable(this.resource.getValueMap().get(ImageResource.PN_LINK_URL, String.class))
                        .map(this.pageManager::getPage).orElse(null);
            } else if (actionsEnabled && getActions().size() > 0) {
                this.targetPage = getTeaserActions().stream().findFirst()
                        .flatMap(com.adobe.cq.wcm.core.components.internal.models.v1.TeaserImpl.Action::getCtaPage)
                        .orElse(null);
            } else {
                targetPage = currentPage;
            }
        }
        return Optional.ofNullable(this.targetPage);
    }

https://github.com/adobe/aem-core-wcm-components/blob/main/bundles/core/src/main/java/com/adobe/cq/w...

 

I want to override this behavior so that external URLs like https://www.example.com are accepted as-is, without validation through PageManager.getPage(). The teaser should behave like v1, if it's a valid URL (internal or external), just pass it through.

Thanks in advance for any insights!

1 Accepted Solution

Avatar

Correct answer by
Community Advisor

@aa_w 

You're right - TeaserImpl is marked as internal and not designed for direct extension. Since it's not part of the public API and getTargetPage() is protected, it’s not accessible from a custom implementation via inheritance.

However, you can still customize the Teaser v2 component to support external URLs by proxying the component and implementing your own model logic.

Recommended Approach: Custom Sling Model + Proxy Component

  1. Create a proxy component
    In your project under /apps, create a proxy to the Core Teaser v2:

    /apps/your-project/components/content/customteaser

    Inside, use the sling:resourceSuperType to inherit from the Core Teaser:

    <!-- /apps/your-project/components/content/customteaser/.content.xml -->
    <jcr:root
      jcr:primaryType="cq:Component"
      sling:resourceSuperType="core/wcm/components/teaser/v2/teaser"
      jcr:title="Custom Teaser"
      componentGroup="Your Project"/>
  2. Create a custom Sling Model
    Implement your own model that adapts from Teaser (the interface), and override getLinkURL() to return the external URL directly.

    @Model(adaptables = SlingHttpServletRequest.class, adapters = Teaser.class, resourceType = "your-project/components/content/customteaser", defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
    public class CustomTeaserImpl implements Teaser {
    
        @Self
        private SlingHttpServletRequest request;
    
        @ScriptVariable
        private Page currentPage;
    
        @ValueMapValue
        @Optional
        private String linkURL;
    
        @Override
        public String getLinkURL() {
            // Return external link directly if set
            if (linkURL != null && linkURL.startsWith("http")) {
                return linkURL;
            }
            return currentPage.getPath(); // fallback
        }
    
        // Implement other required methods like getTitle(), getDescription(), etc.
    }

    You only need to override what’s necessary -getLinkURL() in this case.

  3. Use your component in editable templates or page structure


Santosh Sai

AEM BlogsLinkedIn


View solution in original post

5 Replies

Avatar

Community Advisor

Hi @aa_w,

I think, Teaser v2 or most of the components introduced stricter internal URL resolution via PageManager.getPage(), which prevents external URLs from being recognized correctly in getTargetPage(). To restore the v1-style behavior (i.e., allow any valid URL, including external ones), you’ll need to extend TeaserImpl and override the getTargetPage() method or rely on getLinkURL() directly in your extended model.

Here’s what you can do to achieve your goal:

Override getTargetPage() Gracefully

If your logic requires getTargetPage() (e.g., for internal navigation or link tracking), override it to skip PageManager resolution for external URLs.

@Override
@Nullable
protected Optional<Page> getTargetPage() {
    if (this.targetPage == null) {
        String linkURL = resource.getValueMap().get(ImageResource.PN_LINK_URL, String.class);
        if (StringUtils.isNotEmpty(linkURL)) {
            if (linkURL.startsWith("http://") || linkURL.startsWith("https://")) {
                // External URL — skip PageManager resolution
                this.targetPage = null;
            } else {
                // Internal page — attempt to resolve
                this.targetPage = Optional.ofNullable(pageManager.getPage(linkURL)).orElse(null);
            }
        } else if (actionsEnabled && getActions().size() > 0) {
            this.targetPage = getTeaserActions().stream().findFirst()
                .flatMap(Action::getCtaPage)
                .orElse(null);
        } else {
            targetPage = currentPage;
        }
    }
    return Optional.ofNullable(this.targetPage);
}

This ensures:

  • External links don’t throw off resolution.

  • Internal links still behave as intended.

  • The fallback logic (actions or current page) remains unchanged.

Use getLinkURL() Instead

In most real-world use cases, your teaser template or HTL script should rely on getLinkURL() for the anchor <a href="..."> rather than trying to fetch the resolved page.

Check that your override of getLinkURL() returns the raw URL when set:

@Override
public String getLinkURL() {
    String linkURL = resource.getValueMap().get(ImageResource.PN_LINK_URL, String.class);
    if (StringUtils.isNotEmpty(linkURL)) {
        return linkURL;
    }
    return super.getLinkURL(); // fallback to default behavior
}

This might be sufficient if you don't need the actual Page object but just the URL.

Hope that helps!


Santosh Sai

AEM BlogsLinkedIn


Avatar

Level 2

Thanks @SantoshSai. That would work if I could override getTargetPage(), but the problem is that the TeaserImpl class is internal and those methods are not accessible for extension.

Since TeaserImpl isn’t part of the public API, I can’t extend it or override its logic. The only thing I can work with is the public teaser interface, and unfortunately, that doesn’t include getTargetPage() It only gives access to getLinkURL(), getTitle(), etc.

 

Overriding getLinkURL() doesn't help either as the image resource properties are configured in TeaserImpl and they do not directly use LinkURL. LinkURL is used to check if its a valid page only.

 

So my issue is how can I do override TeaserImpl, could you explain a little more since I'm relatively new to AEM

 

Thanks!

Avatar

Community Advisor

Hi @aa_w ,

Since TeaserImpl is internal and cannot be extended directly, your goal is to customize the component to allow external URLs without altering its internal logic directly. Given that you only have access to the public interface and cannot directly override getTargetPage() or other internal methods, we need a solution that leverages AEM's flexibility while respecting its component architecture.

Here’s a comprehensive solution that can be implemented without modifying TeaserImpl directly, but achieves the desired outcome.

1. Create a Custom Teaser Model to Extend Functionality

You will need to create a custom model that can extend the behavior of the Teaser component using a new class that implements the public Teaser interface.

Since the public Teaser interface doesn't expose getTargetPage() and relies on getLinkURL(), we'll work with getLinkURL() and implement custom logic to differentiate between internal and external URLs.

Custom Teaser Model

You will create a custom class that implements the public Teaser interface and handles the logic for both internal and external URLs.

Example:

package com.example.aem.core.models;

import org.apache.commons.lang3.StringUtils;
import org.apache.sling.api.resource.Resource;
import com.adobe.cq.wcm.core.components.models.Teaser;
import com.adobe.cq.wcm.core.components.models.Link;

import javax.annotation.Nullable;
import java.util.Optional;

public class CustomTeaserImpl implements Teaser {

    private final Resource resource;

    public CustomTeaserImpl(Resource resource) {
        this.resource = resource;
    }

    @Nullable
    @Override
    public String getLinkURL() {
        // Retrieve link URL from the resource
        String linkURL = resource.getValueMap().get("linkURL", String.class);
        
        if (StringUtils.isNotEmpty(linkURL)) {
            // If the URL is external (starts with http:// or https://), return as-is
            if (linkURL.startsWith("http://") || linkURL.startsWith("https://")) {
                return linkURL; // External URL, no validation needed
            } else {
                // For internal URLs, resolve using PageManager or return default if not resolved
                // You can implement page resolution logic here
                return linkURL; // Placeholder for internal URL resolution
            }
        }
        
        // Fallback if no URL exists
        return null;
    }

    @Override
    public String getTitle() {
        return resource.getValueMap().get("title", String.class);
    }

    @Override
    public Optional<Link> getLink() {
        // You can create a Link object based on URL logic if needed
        return Optional.empty();
    }

    // Other methods from the Teaser interface as necessary
}

 

In the above example, we handle both external and internal URLs. External URLs are returned as-is (like the behavior you want from Teaser v1), and internal URLs can be handled according to your existing logic.

The getLinkURL() method is used to return the URL, which you can customize based on whether the link is external or internal.

2. Register the Custom Teaser as a Sling Model

You need to ensure that AEM uses your custom model for the Teaser component instead of the default one.

Create a Sling Model annotation for your custom class:

import org.apache.sling.api.resource.Resource;
import com.adobe.cq.wcm.core.components.models.Teaser;
import org.apache.sling.api.scripting.SlingScriptHelper;

import javax.annotation.PostConstruct;
import org.apache.sling.models.annotations.Model;

@Model(adaptables = Resource.class)
public class CustomTeaserImpl implements Teaser {
    private final Resource resource;
    
    public CustomTeaserImpl(Resource resource) {
        this.resource = resource;
    }
    
    @PostConstruct
    public void init() {
        // Any initialization if required
    }
    
    @Override
    public String getLinkURL() {
        // Your custom logic to handle external and internal URLs
    }
    
    @Override
    public String getTitle() {
        return resource.getValueMap().get("title", String.class);
    }
    
    // Implement other necessary methods here
}

Make sure this model class is correctly registered and is picked up when rendering the Teaser component.

Avatar

Correct answer by
Community Advisor

@aa_w 

You're right - TeaserImpl is marked as internal and not designed for direct extension. Since it's not part of the public API and getTargetPage() is protected, it’s not accessible from a custom implementation via inheritance.

However, you can still customize the Teaser v2 component to support external URLs by proxying the component and implementing your own model logic.

Recommended Approach: Custom Sling Model + Proxy Component

  1. Create a proxy component
    In your project under /apps, create a proxy to the Core Teaser v2:

    /apps/your-project/components/content/customteaser

    Inside, use the sling:resourceSuperType to inherit from the Core Teaser:

    <!-- /apps/your-project/components/content/customteaser/.content.xml -->
    <jcr:root
      jcr:primaryType="cq:Component"
      sling:resourceSuperType="core/wcm/components/teaser/v2/teaser"
      jcr:title="Custom Teaser"
      componentGroup="Your Project"/>
  2. Create a custom Sling Model
    Implement your own model that adapts from Teaser (the interface), and override getLinkURL() to return the external URL directly.

    @Model(adaptables = SlingHttpServletRequest.class, adapters = Teaser.class, resourceType = "your-project/components/content/customteaser", defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL)
    public class CustomTeaserImpl implements Teaser {
    
        @Self
        private SlingHttpServletRequest request;
    
        @ScriptVariable
        private Page currentPage;
    
        @ValueMapValue
        @Optional
        private String linkURL;
    
        @Override
        public String getLinkURL() {
            // Return external link directly if set
            if (linkURL != null && linkURL.startsWith("http")) {
                return linkURL;
            }
            return currentPage.getPath(); // fallback
        }
    
        // Implement other required methods like getTitle(), getDescription(), etc.
    }

    You only need to override what’s necessary -getLinkURL() in this case.

  3. Use your component in editable templates or page structure


Santosh Sai

AEM BlogsLinkedIn


Avatar

Level 4

Hi @aa_w,

Did the shared solution help you out? Please let us know if you need more information. Otherwise kindly consider marking the most suitable answer as ‘correct’.

If you've discovered a solution yourself, we would appreciate it if you could share it with the community.