Follow this guide to replace the built-in cart upsell design with your own layout.
You will:
Turn on the custom template editors
Open Upsells (or Upsell with Discount)
Edit the template code
Compile → Activate → Save
Check the cart preview and your live store
Before you start
Who this is for
Custom templates use HTML, CSS, and React (JSX) code.
If you (or your developer) are comfortable editing JSX → continue.
If not → hire a Shopify developer. Do not guess at the code.
Support note
Our support team cannot help write, debug, or fix custom template code. Please send technical questions to your developer.
What this feature does
Module | Where to open it | What your template replaces |
Upsells | Cart editor → Upsells | The whole upsell section (title + products + buttons) |
Upsell with Discount | Cart editor → Upsell with Discount | The whole discount-upsell section (title + cards + prices + "Save …" + buttons) |
Important:
While your template is Active, the built-in layout options (carousel, grid, spotlight, etc.) are ignored. Buyers see your design.
Product picking, discounts, languages, and add-to-cart still work through the app. You only change how it looks.
Turning the editor off in Settings does not turn off a template that is already Active. To remove it, set the switch to Inactive and Save.
Part 1 — Turn on custom templates
Open the app.
Go to Settings.
Find Custom templates.
Check Enable custom template editors.
Click Save.
Done. You should now see a Custom template card inside Upsells / Upsell with Discount.
Part 2 — Open the template editor
Go to the Cart editor.
Open Upsells (or Upsell with Discount if that is the module you want to customize).
Scroll to the bottom until you see Custom template (badge: Technical skills required).
Notes:
Each Upsell instance (main + duplicates) has its own template. Customize each one separately.
Upsell with Discount has only one instance.
Part 3 — Use the editor buttons (in this order)
Always follow this order after you change code:
Edit code → Format (optional) → Compile → Activate → Save
Button / control | What you should do |
Code box | Edit the template here. It starts with a working default. |
Format | Optional. Cleans up spacing. Does not publish anything. |
Compile | Required. Turns your code into something the cart can run. Wait for the green success message. |
Active / Inactive | Turn Active only after Compile succeeds. |
Reset (arrow icon) | Puts the default starter code back. After Reset, click Compile again. |
Rules to remember
Changing the code alone does nothing until you Compile.
Do not turn Active if Compile failed (red error).
Always Save the cart editor (Shopify save bar) when finished.
Check the cart preview first, then your live store (or a draft theme).
Part 4 — First success (do this before customizing)
Use these steps once so you know the feature works.
Open Custom template on Upsells.
Click Reset (optional — loads the default starter).
Click Compile.
Wait for: Successfully compiled…
Switch to Active.
Save the cart.
Look at the cart preview — the upsell should still appear.
If that works, you are ready to change the design.
To undo later: set Inactive → Save (or Reset → Compile → Activate → Save to restore the starter).
Part 5 — How to write the template (follow along)
What you are typing
You are not writing a normal React project file.
Do not use:
import/exportuseState/useEffectTypeScript
Separate CSS files
You write one JSX block. The app gives you a props object with products, prices, and buttons already prepared.
Imagine this (you only write the inside):
function MyUpsell(props) {
return (
// ← your code goes here
);
}
Rule 1 — Wrap everything
Start and end with a fragment:
<>{/* your UI here */}</>
Rule 2 — Use props for data
The app sends ready-made values. Use them. Do not invent prices or product lists.
You want… | Use… |
Section title |
|
List of products |
|
One product's name |
|
One product's image |
|
One product's price |
|
Add button |
|
Loading placeholders (Upsell) |
|
Rule 3 — Loop over products
Every product card is built the same way:
{
props.products.map((p) => <div key={p.id}>{/* one product card */}</div>);
}
Always set
key={p.id}.Inside the loop, use
p.for that product.
Rule 4 — Show HTML fields the safe way
Titles and prices are HTML strings. Display them like this:
<div dangerouslySetInnerHTML={{ __html: props.titleHtml }} />
<div dangerouslySetInnerHTML={{ __html: p.priceHtml }} />
Do not write {p.priceHtml} as normal text — buyers would see raw HTML tags.
Rule 5 — Wire the Add button exactly like this
<button
type="button"
onClick={() => {
if (!props.isPreview) {
p.onAddToCart();
}
}}
>
{p.isLoading ? "Adding…" : p.buttonText}
</button>
Why:
if (!props.isPreview)avoids odd clicks in the admin preview.p.onAddToCart()also opens the variant picker when a product has options. You do not build that yourself.
Rule 6 — JSX basics (quick reference)
Goal | Write |
CSS class |
|
Inline style |
|
Show if true |
|
If / else |
|
Comment |
|
Part 6 — Build your first custom layout (copy & follow)
Do these edits in order. After each step: Compile → check preview → Save.
Step A — Start from a simple Upsell layout
Open Upsells → Custom template.
Select all code in the editor and replace it with:
<>
{props.showSkeleton ? (
<div id="ia-upsell-component" style={{ padding: "8px 0" }}>
<div
style={{
height: 14,
width: "35%",
background: "#eee",
borderRadius: 6,
marginBottom: 16,
}}
/>
<div style={{ display: "flex", gap: 16 }}>
<div
style={{
width: 80,
height: 80,
background: "#eee",
borderRadius: 6,
}}
/>
<div style={{ flex: 1 }}>
<div
style={{
height: 14,
width: "60%",
background: "#eee",
borderRadius: 6,
marginBottom: 8,
}}
/>
<div
style={{
height: 30,
width: "100%",
background: "#eee",
borderRadius: 6,
}}
/>
</div>
</div>
</div>
) : (
<div id="ia-upsell-component">
<div
style={{ fontSize: 18, fontWeight: 700, marginBottom: 12 }}
dangerouslySetInnerHTML={{ __html: props.titleHtml }}
/>
<div style={{ display: "flex", flexDirection: "column", gap: 12 }}>
{props.products.map((p) => (
<div
key={p.id}
style={{
display: "flex",
gap: 12,
alignItems: "center",
padding: 8,
border: "1px solid #eee",
borderRadius: 8,
}}
>
<img
src={p.imageUrl}
alt={p.title}
width={72}
height={72}
style={{ objectFit: "cover", borderRadius: 6 }}
/>
<div style={{ flex: 1 }}>
<a
href={p.productUrl}
style={{
fontWeight: 600,
textDecoration: "none",
color: "inherit",
}}
>
{p.title}
</a>
<div dangerouslySetInnerHTML={{ __html: p.priceHtml }} />
<button
type="button"
onClick={() => {
if (!props.isPreview) p.onAddToCart();
}}
>
{p.isLoading ? "Adding…" : p.buttonText}
</button>
</div>
</div>
))}
</div>
</div>
)}
</>
Click Compile.
Set Active.
Save.
Confirm the preview shows a simple list of upsell products.
Step B — Add reviews and compare-at price
Inside each product card (after the title), add:
{
p.reviews;
}
{
p.showCompareAtPrice && p.compareAtPriceHtml && (
<div
style={{ textDecoration: "line-through", opacity: 0.6 }}
dangerouslySetInnerHTML={{ __html: p.compareAtPriceHtml }}
/>
);
}
Compile → Save → check preview.
Step C — Add your own colors with CSS
At the top of the fragment (right after <>), add:
<style>{`
#ia-upsell-component.my-upsell .my-add-btn {
background: #111;
color: #fff;
border: 0;
padding: 10px 14px;
border-radius: 6px;
cursor: pointer;
}
#ia-upsell-component.my-upsell .my-add-btn:hover {
background: #e8f58a;
color: #111;
}
`}</style>
Then:
Add
className="my-upsell"to the main#ia-upsell-componentdiv.Add
className="my-add-btn"to your Add button.
Compile → Save → hover the button in preview.
Keep CSS selectors under #ia-upsell-component so styles do not affect the rest of the cart.
Step D — Optional: use the stock design as a fallback
If you get stuck, replace everything with:
<>{props.builtInUpsell}</>
For Upsell with Discount, use:
<>{props.builtInUpsellDiscount}</>
That restores the built-in look while the template stays Active.
Part 7 — Upsell with Discount (same flow)
Open Cart editor → Upsell with Discount.
Scroll to Custom template.
Replace the code with:
<>
<div
id="ia-upsell-discount-component"
style={{
padding: 12,
...props.containerStyleVars,
}}
>
<div
style={{ marginBottom: 12, ...props.titleStyleVars }}
dangerouslySetInnerHTML={{ __html: props.titleHtml }}
/>
<div style={{ display: "flex", gap: 12, overflowX: "auto" }}>
{props.products.map((p) => (
<div
key={p.id}
style={{
minWidth: 160,
maxWidth: 200,
flex: "0 0 auto",
...p.containerStyleVars,
}}
>
<img
src={p.imageUrl}
alt={p.title}
style={{
width: "100%",
height: 120,
objectFit: "cover",
...p.imageStyleVars,
}}
/>
<div style={p.titleStyleVars}>{p.title}</div>
{p.reviews}
<div
style={p.priceStyleVars}
dangerouslySetInnerHTML={{ __html: p.priceHtml }}
/>
{p.showOriginalPrice && p.originalPriceHtml && (
<div
style={{
textDecoration: "line-through",
opacity: 0.65,
...p.originalPriceStyleVars,
}}
dangerouslySetInnerHTML={{ __html: p.originalPriceHtml }}
/>
)}
<div
style={p.savingsStyleVars}
dangerouslySetInnerHTML={{ __html: p.saveTextHtml }}
/>
<button
type="button"
style={{ width: "100%", marginTop: 8, ...p.buttonStyleVars }}
disabled={p.isLoading}
onClick={() => {
if (!props.isPreview) p.onAddToCart();
}}
>
{p.isLoading ? "…" : p.buttonText}
</button>
</div>
))}
</div>
</div>
</>
Compile → Active → Save.
Confirm discounted price, original price, and "Save …" text show correctly.
Part 8 — Optional: horizontal carousel with arrows (Upsell)
If you want left/right arrows, keep these names exactly:
Piece | Required |
Root |
|
Wrapper | class |
List | class |
Each item | class |
Wire the arrows:
<div className="ia-carousel-button" onClick={props.onCarouselPrevious}>
‹
</div>
<ul className="ia-carousel-list">
{props.products.map((p) => (
<li key={p.id} className="ia-carousel-item">
{/* card */}
</li>
))}
</ul>
<div className="ia-carousel-button" onClick={props.onCarouselNext}>
›
</div>
Swipe/drag is not included in custom templates. Use arrows, or fall back to {props.builtInUpsell}.
Part 9 — Test before you go live
Work through this list:
Template Compiles with a green message.
Switch is Active.
Cart editor is Saved.
Preview shows title, products, prices, and Add buttons.
(Discount) Savings text and original price look correct.
On the live store (or draft theme):
Add a product that should trigger the upsell.
Open the cart.
Click Add on a single-variant upsell.
Click Add on a multi-variant upsell and confirm the option sheet opens.
Loading placeholders look OK (Upsell) while products load.
Something wrong? Fix it here
What you see | What to do |
No Custom template card | Settings → enable editors → Save |
Code changed but cart looks the same | Compile → Active → Save |
Red Compile error | Fix the JSX (often a missing |
Upsell section missing | Make sure products match; temporarily use |
Add does nothing in preview | Normal — test on the live cart |
Multi-variant product won't add | Call |
Carousel arrows do nothing | Check the required id/classes in Part 8 |
Colors from the old layout settings missing | Expected when Active — style in your template instead |
Turned editors off but custom UI remains | Set template to Inactive and Save |
Appendix A — Upsell props (reference)
Use these names in your code. Values are provided by the app.
Whole section (props.)
Prop | Meaning |
|
|
| Section title (HTML) |
| Title styles from settings |
| Array of product cards |
| Show loading placeholders |
| Arrow helpers |
| Arrow color |
| Carousel sizing |
| Full built-in UI fallback |
| Extra settings (usually not needed) |
Each product (p. inside .map)
Prop | Meaning |
| Unique key |
| Product name |
| Link to product page |
| Image URL |
| Formatted price (HTML) |
| Compare-at price (HTML) |
| Whether to show compare-at |
| Add / Choose options label |
| Add in progress |
| Add (or open variant sheet) |
| Reviews widget — render as |
| Style objects from settings |
Keep id="ia-upsell-component" on the root if you use carousel arrows.
Appendix B — Upsell with Discount props (reference)
Whole section (props.)
Prop | Meaning |
| Admin preview flag |
| Title (HTML) |
| Title styles |
| Section background / text color |
| Discount product cards |
| Built-in UI fallback |
| Optional arrows |
| Always |
Each product (p.)
Prop | Meaning |
| Unique key |
| Product name |
| Product link |
| Image URL |
| Discounted price (HTML) |
| Original price (HTML) |
| Show original price |
| "Save …" badge (HTML) |
| Same idea as Upsell |
| Discount details (optional) |
| Style objects |
Root id for this module: id="ia-upsell-discount-component".
Appendix C — Do / Don't
Do | Don't |
Compile after every change you care about | Expect edits to publish without Compile |
Guard Add with | Call cart APIs yourself |
Use | Calculate your own money strings |
Render | Put reviews inside |
Call | Build your own multi-variant picker |
Scope CSS under the section id | Use global CSS that restyles the whole cart |
Start from Part 6's starter | Rewrite everything from scratch on day one |
Quick path (summary)
Settings → Enable custom template editors → Save
Cart editor → Upsells (or Upsell with Discount) → Custom template
Paste a starter from Part 6 or Part 7
Compile → Active → Save
Test in preview, then on the live cart
Customize styles one step at a time
That is the full flow end to end.
