Skip to main content
A custom template lets you override how an individual block renders. Instead of the block’s built-in UI, the cart renders your own JSX, using the same data the block would normally use. It’s a cross-cutting capability rather than a block of its own: most blocks expose it from their Code tab. This page covers what applies to every block. For the props a specific block hands you, jump to the block’s own reference.

Custom template vs. Custom code block

These sound similar but do different things:
  • A custom template replaces the rendering of an existing block with your own markup, and hands you that block’s own data (the Header’s title and item count, the Summary’s totals, and so on). It doesn’t add anything new; it restyles one block.
  • The Custom code block adds a new block of arbitrary HTML or React anywhere in the cart.
Reach for a custom template when the built-in block is almost right but you need a different layout or markup. Reach for a Custom code block when you want to add something the built-in blocks don’t cover.

Using a custom template

  1. Select a block in the editor and open its Code tab.
  2. Edit the default template. Custom templates are JSX only (the HTML-or-JSX choice is exclusive to the Custom code block).
  3. Click Compile. Compiling strips the types and transpiles the JSX, so it catches syntax errors. Type errors don’t stop a compile — the editor flags those inline as you type, with the same IntelliSense that autocompletes the block’s props.
  4. Turn the template on to make the cart use it instead of the built-in rendering.
  5. Reset to default restores the block’s original template at any time.

Writing a template with AI

The Code tab includes a Copy AI prompt button (✦ wand icon). Clicking it copies a self-contained brief to your clipboard that you can paste directly into an AI chat session (Claude, ChatGPT, or similar). The prompt includes everything the AI needs to write a valid template for that specific block:
  • The compile rules (single expression, no export default, no imports)
  • The exact props the block receives, matching what the editor’s IntelliSense shows
  • The locked function signature the editor enforces
  • Block-specific rules (money formats, which handlers to wire, accessibility requirements)
  • A fill-in section where you paste your current template and describe the change you want
After copying, open an AI session, paste the prompt, fill in the two blanks at the bottom (your current template and the change you want), and send. The AI returns a complete template you can paste back into the editor and compile.
Paste your existing template into the fill-in section rather than leaving it blank. The AI uses it as the starting point, so any customization you’ve already made carries forward rather than being replaced by the default.
The prompt is specific to each block. The Copy AI prompt button only appears on blocks that support custom templates.
The default template you start from is a working copy of the block’s built-in markup, so you always have a correct, rendering reference to modify rather than a blank page. Reach for Reset to default whenever you want that reference back.It isn’t always a byte-for-byte match. The Header’s default template also renders logoUrl, which the built-in markup has no placement for, so turning that template on is how an uploaded header image first appears.

What your template replaces

A template replaces the block’s rendering entirely. There’s no wrapper left around your JSX, which has consequences worth knowing before you start deleting things:
The Design tab is the one people are caught by. While a custom template is active, the Design tab’s fields are disabled and a warning icon appears next to the “Design” heading. Hover over the icon to see why. Style the block from your template instead, either inline or with your own CSS. The fields re-enable as soon as you turn the custom template off.
What you keep: the block’s position in the cart, its visibility toggle, its settings (which still feed the props you receive), the cart’s Custom CSS panel, and the built-in loading skeleton. That last one surprises people. The block checks whether the cart is still loading before it reaches your template, so the built-in skeleton renders during load and your template only runs once the cart is ready. You don’t have to build a loading state.

What’s available inside a template

Your template is a single function component. It compiles from TSX, so type annotations are allowed and stripped at compile time. That’s why the default templates are written with them:
The signature line and the closing brace are locked — the editor won’t let you edit either, and hovering shows “Locked — this line can’t be edited.” You write the body between them. Reset to default is the one thing that can replace them. What else matters:
  • You get five hooks: useState, useEffect, useMemo, useRef, and useCallback. Plus Fragment, for <>…</>.
  • There are no imports. You can’t import anything, and there’s no React object in scope, so no React.useReducer, no React.Children. If a hook isn’t in the list above, it isn’t available.
  • Props are read-only. Mutating a prop won’t do anything useful. To change the cart, use the handler props the block gives you (onClose, increment, selectPlan, and so on) rather than writing to props directly.
  • window is reachable, so a template can call the Cart SDK via window.aftersell.cart when it needs something the block’s props don’t cover.

Conventions across every block

Three rules hold everywhere, and knowing them removes most of the guesswork:
  • *Html props are pre-sanitized rich text. Render them with dangerouslySetInnerHTML. They’ve already been through the cart’s sanitizer, and merchant tokens like {{total_price}} are already resolved.
  • Prices that arrive as string are already formatted in the shop’s money format. Prices as number are in cents. A block gives you one or the other, and each block’s table says which.
  • isLoading is always false inside a template. The block renders its built-in skeleton and only calls your template once the cart has loaded, so the prop is passed for completeness rather than for you to branch on.
A few blocks return nothing at all in certain states, so your template is never called with empty data. The Rewards template never sees an empty milestones, and the Subscription upgrade template never sees a null view. Each block’s reference notes where this applies, so you can skip the empty-state branch.

Styling a custom template

The default template you start from carries the block’s classnames. How you style your edits depends on how far you move from that starting point.

The two class families

Every element in a default template carries a paired classname, and they do very different jobs: So cart-internal-header__title is what makes the title look like the built-in title, and cart-external-header__title is the handle you’re meant to grab when you want to change how it looks.

Small changes: keep both classnames

If you’re reordering elements, relabelling, or adding something inside the existing structure, leave the classnames alone. You keep the built-in look for free, and you restyle through Custom CSS targeting the cart-external-* hooks.

Restructuring: drop both classnames

Once you’re changing the DOM structure rather than tweaking it, take both families off your markup and use your own classnames instead. There’s a separate reason for each. Drop cart-internal-* because the built-in CSS was written for the built-in DOM. Keep those classes on restructured markup and you inherit layout rules that assume elements you no longer have: flex containers expecting different children, spacing between elements that moved, positioning relative to something you removed. This usually surfaces as your own CSS “not working” when the built-in rules are the ones winning.
Drop cart-external-* because it’s a shared name, not yours. Those classnames mean something specific on the built-in markup, and your Custom CSS is written once for the whole cart. If a restructured template reuses them, any rule you write targets both your structure and the built-in one.That goes wrong the moment you turn the custom template off: the block reverts to its built-in markup, and your CSS is still pointing at it, now styling a DOM it was never written for. Your own prefix keeps the two cleanly separated, so toggling a template off is a clean revert.
Two ways to style what you’ve built:

Option 1: your own classnames plus Custom CSS

Best for anything you’ll maintain or reuse. Give your classes a prefix nobody else will collide with, usually your store or brand name:
Then in the cart editor, select Cart settings in the left panel and open the Custom CSS tab on the right:
A prefix matters more than it looks. Without one, a class like .header or .title risks colliding with the cart’s own classes, another app’s template, or a future block.

Option 2: inline styles

No CSS panel round-trip, and everything lives in one place:
Good for layout scaffolding and one-offs. Its limits are the usual ones: no :hover or other pseudo-classes, no media queries, and no reuse across blocks. Reach for Option 1 once you want any of those.

Picking an approach

The cart renders in a shadow root, so your theme’s stylesheet can’t reach inside it. Styles for a custom template have to come from the cart’s own Custom CSS panel or from inline styles, not from your theme. See Custom CSS.

When a template fails

A broken template never breaks the cart. The block renders nothing and everything around it keeps working, which is safe but easy to miss: a blank space where your block should be is the symptom. Because the block silently disappears rather than erroring visibly, always check a template in preview before publishing. If a block has gone missing, open the browser console first. Two things worth guarding for, since both crash a template that assumes otherwise:
  • Nullable props. Many props are null in normal conditions (logoUrl with no logo, imageUrl with no image, variantTitle on a single-variant product). Check before you use them.
  • Arrays that can be empty. discountTags and discountCodes are [] far more often than not.

Limitations

  • Custom templates are display overrides. To run logic against the cart (subscribe to events, add items, react to changes), use Custom scripts and the Cart SDK.
  • Almost every block supports one. The exceptions are the Express payments block, which hosts Shopify’s own payment buttons, and the Cart items container itself, though the Product row inside it does support a custom template.
  • A template can’t change what a block fundamentally does. It changes how the block’s data is presented, not the data or the behavior behind it.

Props for each block

Every block passes its own data. The full prop table, with types and a worked example, lives on that block’s page: The Custom code block is the one surface that adds markup rather than replacing a block’s rendering, so its props are different: the whole cart, plus an add-to-cart action. See Custom code blocks → Props.