Visual Content Fragments - Deliver with the Publish URL visual-content-fragments-deliver-with-the-publish-url
When a Content Fragment that is based on a model with one, or multiple, attached HTML templates is published, the rendered HTML of that fragment is made available via the Adobe Experience Manager (AEM) as a Cloud Service publish tier at a URL with this structure:
https://publish-p<programId>-e<envId>.adobeaemcloud.com/adobe/stable/previewtemplates/contentFragments/<templateId>/<fragmentId>/<variation>.html
This URL returns a self-contained HTML document (including inline CSS and structure) that can be embedded in any web context.
Embedding Techniques — Overview embedding-techniques-overview
There are three distinct approaches for consuming HTML from a Visual Content Fragment on a host page. Each comes with distinct characteristics around style isolation, layout behavior, accessibility, and complexity.
fetch() the URL, inject the response HTML into a <div> via innerHTML<iframe src="publishURL">fetch() the HTML, inject into an attached Shadow DOM rootwidth/height or JS-based auto-resizetitle attributefetch() is not indexed by most crawlers<script> tagsInline Element (fetch + innerHTML) inline-element-fetch-and-innerhtml
The simplest approach:
- fetch the Publish URL
- inject the HTML into a container element
An example of inline element embedding:
<div id="cf-container"></div>
<script>
fetch("<publish-url>")
.then(r => r.ok ? r.text() : Promise.reject(r.status))
.then(html => {
document.getElementById("cf-container").innerHTML = html;
})
.catch(err => console.error("Failed to load fragment", err));
</script>
When to use:
- Rapid prototyping or proof-of-concept pages
- Same-origin contexts where you control both the host page and fragment styles
- When maximum layout flexibility is more important than style encapsulation
<style> blocks, font-face declarations, and element selectors) merge into the host page’s cascade.iframe iframe
Load the Publish URL directly as the src of an <iframe>. No JavaScript is needed.
An example of iframe embedding:
<iframe
src="<publish-url>"
title="Content Fragment Preview"
width="100%"
height="600"
frameborder="0"
style="border: none;"
></iframe>
You can also automatically resize the iframe (this is optional).
To dynamically size the iframe to its content height (avoiding scrollbars), use a postMessage pattern or an appropriate library.
An example of a lightweight approach is:
<iframe id="cf-iframe" src="<publish-url>" title="Content Fragment Preview"
width="100%" frameborder="0" style="border:none; overflow:hidden;"
onload="this.style.height = this.contentDocument.documentElement.scrollHeight + 'px';"
></iframe>
onload auto-resize approach above only works for same-origin iframes.postMessage-based solution or to set a fixed height.When to use:
- Edge Delivery Services embed block (the default integration — see section below)
- Contexts where full CSS/JS isolation is critical
- Cross-origin embedding where CORS is not configured
- Quick, zero-code integration (just paste the URL)
Custom Element + Shadow DOM (Recommended) custom-element-and-shadow-dom-recommended
Define a reusable <cf-visualization> Custom Element that fetches the Publish URL and injects the HTML into an encapsulated Shadow DOM root.
This element provides:
- Shadow DOM isolation
- The fragment’s markup and styles are encapsulated in a shadow root, preventing collisions with the host page’s CSS cascade.
- Inline layout participation
- The rendered content participates in the host document’s normal flow, responding to container sizing and flexbox/grid contexts without manual dimension management.
- Single browsing context
- No secondary document context is created; the fragment content shares the page’s JavaScript runtime and is fully traversable by assistive technologies.
- Minimal overhead
- A single
fetchcall retrieves the pre-rendered HTML from the publish tier. No client-side rendering framework is required.
- A single
To define the Custom Element, include the following script once per page. All <cf-visualization> instances on the page will use this definition:
<script>
class CfVisualization extends HTMLElement {
connectedCallback() {
const src = this.getAttribute("src");
if (!src) return;
const shadow = this.attachShadow({ mode: "open" });
fetch(src)
.then((r) => (r.ok ? r.text() : Promise.reject(r.status)))
.then((html) => {
shadow.innerHTML = html;
})
.catch((err) => {
console.error("cf-visualization: failed to load", src, err);
});
}
}
if (!customElements.get("cf-visualization")) {
customElements.define("cf-visualization", CfVisualization);
}
</script>
To use the Custom Element:
<cf-visualization src="<publish-url>"></cf-visualization>
When to use:
- AEM Sites pages using Core Components (this is the built-in behavior)
- External/third-party websites that need a clean, reusable integration
- Any context where you need both style isolation and layout-flow participation
Integration with Edge Delivery Services (Embed Block) integration-with-edge-services-embed-block
In Edge Delivery Services, the Publish URL is consumed through an Embed block, which renders it as an <iframe>.
-
Ensure the Embed block exists in your project.
If your EDS project does not already include the Embed block, copy it from the aem-block-collection repository:
code language-cmdline # From the aem-block-collection repo, copy blocks/embed/ into your project's blocks/ directory cp -r aem-block-collection/blocks/embed/ your-eds-project/blocks/embed/ -
Author the embed in the Document Authoring editor (in Edge Delivery Services)
In Document Authoring, blocks are represented as tables. To add a visual Content Fragment embed:
table 0-row-1 1-row-1 embed (paste the Publish URL as a hyperlink) Alternatively, if your project or Sidekick is configured with the Embed block in its block library, you can insert it via the slash menu and paste the Publish URL into the block content.
-
Result
The Embed block renders the Publish URL inside an
<iframe>. The fragment content loads in full CSS isolation within the EDS page layout.
Integration - AEM Sites with Core Components integration-aem-sites-with-core-components
The Content Fragment Core Component (core/wcm/components/contentfragment/v1/contentfragment) has built-in support for rendering Visual Content Fragments using the Customer Element + Shadow DOM technique.
How it works:
-
Author mode:
When the component’s
displayModeis set tovcf, the authoring clientlib (vcfRenderer.js) fetches the fragment HTML from the preview API and renders it inline in the authoring canvas.An example of an Author Preview Endpoint is:
code language-html GET /adobe/sites/cf/fragments/{fragmentId}/preview?templateId={templateId}&variation={variation} -
Publish mode:
On the published page (
wcmmode.disabled), the HTL template renders an inline script that fetches from the Publish URL and injects the HTML into a Shadow DOM root.An example Core Component Visual Content Fragment (templates.html):
code language-html <div class="cmp-contentfragment cmp-contentfragment--vcf" data-cmp-contentfragment-id="{fragmentId}" data-cmp-contentfragment-vcf-template="{templateId}" data-cmp-contentfragment-variation="{variation}"> <!-- Only rendered when wcmmode.disabled (publish) --> <div data-vcf-url="{vcfPublishUrl}" class="cmp-contentfragment__vcf-loader" style="display:none"></div> <script> (function() { var script = document.currentScript; var loader = script.previousElementSibling; var el = script.parentElement; if (!el || !loader) return; var url = loader.dataset.vcfUrl; if (!url) return; loader.remove(); var shadow = el.attachShadow({ mode: "open" }); var body = document.createElement("body"); body.style.display = "none"; shadow.appendChild(body); fetch(url) .then(function(r) { return r.ok ? r.text() : Promise.reject(r.status); }) .then(function(html) { body.innerHTML = html; body.style.display = ""; }); })(); </script> </div>Publish URL Format:
The Sling Model (
ContentFragmentImpl) builds the Publish URL using the following pattern:code language-html /adobe/experimental/previewtemplates-expires-20260301/contentFragments/{templateId}/{fragmentId}/{variation}.htmlThis relative URL is resolved against the publish host at runtime.
Integration with External Sites integration-with-external-sites
For non-AEM websites, use the Customer Element + Shadow DOM technique. This gives you a clean, declarative integration without framework dependencies.
An example is:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Product Page</title>
</head>
<body>
<h1>Product Details</h1>
<p>Some host-page content here...</p>
<!-- Embed the Content Fragment visualization -->
<cf-visualization
src="https://publish-p12345-e67890.adobeaemcloud.com/adobe/experimental/previewtemplates-expires-20260301/contentFragments/product_template/abc-123/master.html"
></cf-visualization>
<p>More host-page content below the fragment...</p>
<!-- Custom Element definition (include once) -->
<script>
class CfVisualization extends HTMLElement {
connectedCallback() {
const src = this.getAttribute("src");
if (!src) return;
const shadow = this.attachShadow({ mode: "open" });
fetch(src)
.then(r => r.ok ? r.text() : Promise.reject(r.status))
.then(html => { shadow.innerHTML = html; })
.catch(err => console.error("cf-visualization: failed to load", src, err));
}
}
if (!customElements.get("cf-visualization")) {
customElements.define("cf-visualization", CfVisualization);
}
</script>
</body>
</html>
<cf-visualization> elements on the same page with different src URLs. The Custom Element definition only needs to be included once.CORS and Security Considerations cors-and-security-considerations
/adobe/** path with configurable allowed origins.The Inline Element (fetch + innerHTML) 1 and Customer Element + Shadow DOM techniques (which use
fetch()) require the host page’s origin to be in the allowed list.The iFrame technique does not require CORS.
Content-Security-Policy or X-Frame-Options headers on the published HTML. If your CDN or Dispatcher adds these headers, verify that they permit framing (for iFrame) or fetch() access (for inline/shadow DOM) from your host origins.Choose the appropriate technique choose-the-appropriate-technique
Use the following as a decision guide to help you choose the appropriate technique: