Cracking the Code: Dynamic Content in Shopify Checkout Extensions (No Raw HTML Allowed!)

Hey store owners and fellow developers!

Lately, there's been a buzzing discussion in the Shopify community that really hits home for anyone building custom experiences on the platform. It's all about bringing dynamic content into your Checkout and Thank-you UI extensions. Specifically, the question came up: "Can Checkout / Thank-you UI extensions render HTML from an external API, or only Shopify components?" It's a fantastic question from @tushar_123, and the community really dug deep to find the answer. Let's break down what we learned, because it's super important for how you approach building these extensions.

The Core Challenge: HTML vs. Shopify's Sandbox

Many of us are used to building web experiences where our backend APIs spit out raw HTML, and our frontend just injects it into the page. It's quick, flexible, and we have full control over the layout. But as @tushar_123 discovered, that approach hits a wall with Shopify's Checkout and Thank-you UI extensions. He wanted to take innerHTML generated by an existing JavaScript renderer and dynamically convert it into Shopify-supported components like s-modal, s-text, s-image, etc., at runtime, because the layout could change with every API response.

The unanimous answer from community experts like Priyasha, Shopplaza_team, AhsanMunir, and v.marychenka was a resounding "no." And honestly, it makes a lot of sense when you understand why.

Why Raw HTML Injection is a No-Go

Here's the deal: Shopify's Checkout and Thank-you UI extensions run in a super locked-down sandbox. This isn't just a minor technicality; it's a fundamental security decision. As AhsanMunir eloquently put it, "Checkout UI extensions don't have access to the real checkout DOM and can't render arbitrary HTML. They can only render custom HTML elements provided by Shopify." There are no escape hatches like iframes or dangerouslySetInnerHTML. This strict environment prevents malicious code (XSS), ensures consistent accessibility, and maintains a reliable, secure payment experience for your customers. It's a deliberate sandbox boundary, not a limitation they haven't gotten around to lifting.

The Solution: Server-Side Translation is Your Best Friend

So, if you can't inject HTML, and you have an existing renderer that produces HTML, what do you do? This is where the community really shone, with VikashJ offering a brilliant, actionable solution that addresses @tushar_123's constraint of not modifying the existing renderer.

The key insight is to perform the "HTML-to-structured-data" conversion on your own server, not inside the extension. Here's how that works:

  1. Your Existing Renderer Stays Put: If your current renderer is JavaScript-based and outputs HTML, it can likely still run in a Node.js environment on your backend. You could use tools like jsdom or Cheerio (a server-side jQuery-like HTML parser) to execute your renderer and capture its HTML output.
  2. Server-Side HTML Parsing: Once you have that HTML string on your server, you have full control. You then use a parser like Cheerio to walk the DOM tree and extract the structured data your extension needs. Think of it as translating:

    becomes heading,

    becomes body text, becomes image_url, and

  3. Return Structured JSON: Your backend API then returns this clean, structured JSON object (e.g., { heading, body: [...], image, cta }) to your Shopify UI extension. This JSON is the "contract" the extension expects.

This approach means your existing renderer's logic, campaign configuration, and styling rules run exactly as they do today. The only new piece is a thin server-side layer that acts as a translator, ensuring the data is in a Shopify-friendly format before it ever reaches the extension's sandbox.

Building the UI with Shopify Components

Once your extension receives that beautiful, structured JSON, rendering the UI becomes straightforward using Shopify's native components:

  • No Official HTML-to-Component Adapter: As Shopplaza_team and HotspotStudio confirmed, Shopify doesn't provide a magical adapter to convert arbitrary screen JSON into s-* components. You'll need to write this mapping yourself.
  • Your Own Generic Mapper: But don't worry, it's not as daunting as it sounds! You can create a simple, generic mapper within your extension code. This mapper iterates through your JSON's structured fields and dynamically creates the corresponding Shopify components. HotspotStudio provided a great example of what this might look like:
const c => s.screenType === "contentscreen");

const modal = document.getElementById("campaign-modal");
modal.heading = contentscreen.header;

for (const node of contentscreen.body) {
  if (node.type === "text")  modal.append(Object.assign(document.createElement("s-text"), {textContent: node.value}));
  if (node.type === "image") modal.append(Object.assign(document.createElement("s-image"), {src: node.url, alt: node.alt ?? ""}));
  if (node.type === "cta")   { /* s-button + your action handler */ }
}

Important Considerations for Implementation

Modal Opening Behavior

One crucial point brought up by HotspotStudio and v.marychenka is how modals open. An s-modal cannot be opened programmatically (e.g., directly after an API response). It must be triggered by buyer interaction. The solution? Have the button that submits your form also be the activator for the modal. It opens immediately with a loading state, then you fire your API request in parallel, and fill the modal content once the response arrives. This keeps it to a single, seamless interaction for the buyer.

Styling Limitations

This is another area where flexibility is reduced. You can't inject custom CSS. Any visual styling from your original HTML renderer will need to be adapted to the available component props (like tone, variant, size) or dropped. Shopify's branding settings (available for Plus merchants via Admin GraphQL API) do reach UI extensions, but this is a one-time setup, not dynamic per-campaign styling.

Network Access

Don't forget to enable network access for your extension! As v.marychenka pointed out, you'll need to declare network_access = true under [extensions.capabilities] in your shopify.extension.toml file. Also, ensure your backend API returns Access-Control-Allow-Origin: * to avoid CORS issues.

Putting It All Together: Your Step-by-Step Approach

Based on the community's collective wisdom, here's a clear path to get your dynamic content working in Shopify Checkout UI extensions:

  1. Adapt Your Backend: The Translation Layer
    • Run your existing HTML-generating renderer on your backend server.
    • Implement a server-side parser (e.g., using Node.js with Cheerio) to consume the HTML output from your renderer.
    • Extract the dynamic content (header, body, image URL, CTA details) and transform it into a structured JSON object.
    • Configure your API to return this structured JSON.
    • Set Access-Control-Allow-Origin: * in your API's response headers.
  2. Configure Your Shopify Extension
    • In your shopify.extension.toml file, add network_access = true under the [extensions.capabilities] section.
    • Define an s-modal component in your extension's UI, initially with a loading state.
    • Map your user interaction (e.g., the form submission button) to open the s-modal using command="--show" and commandFor.
  3. Build Dynamic UI in Your Extension
    • When the user triggers the modal, make an asynchronous call to your backend API to fetch the structured JSON data.
    • Implement a client-side mapping function within your extension to iterate through the received JSON object.
    • Dynamically create and append Shopify components (s-text, s-image, s-button) to your s-modal based on the JSON's structure and content.
    • Update the modal's content with the dynamic data once the API response arrives.

This approach respects Shopify's secure sandbox environment while allowing you to maintain the dynamic nature of your content and leverage your existing campaign logic. It's a bit of a shift if you're used to raw HTML, but it's the supported and secure way to build powerful, custom experiences within Shopify's checkout flow. Big thanks to everyone in the community thread for these invaluable insights!

Share:

Use cases

Explore use cases

Agencies, store owners, enterprise — find the migration path that fits.

Explore use cases