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.
Using a custom template
- Select a block in the editor and open its Code tab.
- Edit the default template. Custom templates are JSX only (the HTML-or-JSX choice is exclusive to the Custom code block).
- 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.
- Turn the template on to make the cart use it instead of the built-in rendering.
- 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
The prompt is specific to each block. The Copy AI prompt button only appears on blocks that support custom templates.
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:
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:- You get five hooks:
useState,useEffect,useMemo,useRef, anduseCallback. PlusFragment, for<>…</>. - There are no imports. You can’t
importanything, and there’s noReactobject in scope, so noReact.useReducer, noReact.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. windowis reachable, so a template can call the Cart SDK viawindow.aftersell.cartwhen 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:*Htmlprops are pre-sanitized rich text. Render them withdangerouslySetInnerHTML. They’ve already been through the cart’s sanitizer, and merchant tokens like{{total_price}}are already resolved.- Prices that arrive as
stringare already formatted in the shop’s money format. Prices asnumberare in cents. A block gives you one or the other, and each block’s table says which. isLoadingis alwaysfalseinside 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 thecart-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. Dropcart-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.
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:.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::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
nullin normal conditions (logoUrlwith no logo,imageUrlwith no image,variantTitleon a single-variant product). Check before you use them. - Arrays that can be empty.
discountTagsanddiscountCodesare[]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.