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);
}
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!
Solved! Go to Solution.
Views
Replies
Total Likes
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.
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"/>
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.
Use your component in editable templates or page structure
Views
Replies
Total Likes
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:
getTargetPage()
GracefullyIf 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.
getLinkURL()
InsteadIn 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!
Views
Replies
Total Likes
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!
Views
Replies
Total Likes
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.
<sly data-sly-use.teaser="com.example.aem.core.models.CustomTeaserImpl">
<a href="${teaser.linkURL}">${teaser.title}</a>
</sly>
This will ensure that when AEM renders the Teaser, it uses your custom logic to resolve the link URL, and external URLs will be passed through as-is, just like in Teaser v1.
4. Ensure Proper Configuration in the Sling Model
Make sure your Sling model is correctly adapted and that it can be used in the component. You may need to ensure the correct mapping in component properties and that the Teaser is properly associated with your custom implementation.
Regards,
Amit
Views
Replies
Total Likes
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.
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"/>
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.
Use your component in editable templates or page structure
Views
Replies
Total Likes
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.
Views
Replies
Total Likes
Views
Likes
Replies