Customising Teaser V2 Component to Support External URLs (like v1) | Community
Skip to main content
Level 2
May 9, 2025
Solved

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

  • May 9, 2025
  • 1 reply
  • 723 views

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:

@9944223 @126844 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/wcm/core/components/internal/models/v2/TeaserImpl.java#L110C1-L127C6

 

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!

Best answer by SantoshSai

@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

1 reply

SantoshSai
Community Advisor
Community Advisor
May 9, 2025

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
aa_wAuthor
Level 2
May 10, 2025

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!

AmitVishwakarma
Community Advisor
Community Advisor
May 11, 2025

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.