Skip to main content

How to use Custom Templates

Replace the built-in cart upsell design with your own HTML/CSS/JSX layout: enable the editors, edit the template, compile, activate, and test.

A
Written by Aditya Singh

Follow this guide to replace the built-in cart upsell design with your own layout.

You will:

  1. Turn on the custom template editors

  2. Open Upsells (or Upsell with Discount)

  3. Edit the template code

  4. Compile → Activate → Save

  5. 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

  1. Open the app.

  2. Go to Settings.

  3. Find Custom templates.

  4. Check Enable custom template editors.

  5. Click Save.

Done. You should now see a Custom template card inside Upsells / Upsell with Discount.


Part 2 — Open the template editor

  1. Go to the Cart editor.

  2. Open Upsells (or Upsell with Discount if that is the module you want to customize).

  3. 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

  1. Changing the code alone does nothing until you Compile.

  2. Do not turn Active if Compile failed (red error).

  3. Always Save the cart editor (Shopify save bar) when finished.

  4. 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.

  1. Open Custom template on Upsells.

  2. Click Reset (optional — loads the default starter).

  3. Click Compile.

  4. Wait for: Successfully compiled…

  5. Switch to Active.

  6. Save the cart.

  7. 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 / export

  • useState / useEffect

  • TypeScript

  • 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

props.titleHtml

List of products

props.products

One product's name

p.title (inside the loop)

One product's image

p.imageUrl

One product's price

p.priceHtml

Add button

p.onAddToCart()

Loading placeholders (Upsell)

props.showSkeleton

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

className="my-card" (not class=)

Inline style

style={{ display: "flex", gap: "12px" }}

Show if true

{p.showCompareAtPrice && ( <div>…</div> )}

If / else

{props.showSkeleton ? ( …loading… ) : ( …content… )}

Comment

{/* note */}


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

  1. Open Upsells → Custom template.

  2. 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>
  )}
</>

  1. Click Compile.

  2. Set Active.

  3. Save.

  4. 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:

  1. Add className="my-upsell" to the main #ia-upsell-component div.

  2. 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)

  1. Open Cart editor → Upsell with Discount.

  2. Scroll to Custom template.

  3. 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>
</>

  1. Compile → Active → Save.

  2. 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

id="ia-upsell-component"

Wrapper

class ia-upsell-custom-carousel

List

class ia-carousel-list

Each item

class ia-carousel-item

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:

  1. Template Compiles with a green message.

  2. Switch is Active.

  3. Cart editor is Saved.

  4. Preview shows title, products, prices, and Add buttons.

  5. (Discount) Savings text and original price look correct.

  6. 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.

  7. 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 } or />), click Format, Compile again

Upsell section missing

Make sure products match; temporarily use {props.builtInUpsell}

Add does nothing in preview

Normal — test on the live cart

Multi-variant product won't add

Call p.onAddToCart() — do not build your own variant menu

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

isPreview

true in admin preview

titleHtml

Section title (HTML)

titleStyleVars

Title styles from settings

products

Array of product cards

showSkeleton

Show loading placeholders

onCarouselPrevious / onCarouselNext

Arrow helpers

arrowColor

Arrow color

carouselItemWidth / carouselPadding

Carousel sizing

builtInUpsell

Full built-in UI fallback

styles / shopLocale / moneyFormat

Extra settings (usually not needed)

Each product (p. inside .map)

Prop

Meaning

id

Unique key

title

Product name

productUrl

Link to product page

imageUrl

Image URL

priceHtml

Formatted price (HTML)

compareAtPriceHtml

Compare-at price (HTML)

showCompareAtPrice

Whether to show compare-at

buttonText

Add / Choose options label

isLoading

Add in progress

onAddToCart

Add (or open variant sheet)

reviews

Reviews widget — render as {p.reviews}

containerStyleVars, imageStyleVars, titleStyleVars, priceStyleVars, originalPriceStyleVars, buttonStyleVars

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

isPreview

Admin preview flag

titleHtml

Title (HTML)

titleStyleVars

Title styles

containerStyleVars

Section background / text color

products

Discount product cards

builtInUpsellDiscount

Built-in UI fallback

onCarouselPrevious / onCarouselNext

Optional arrows

showSkeleton

Always false here (you can ignore it)

Each product (p.)

Prop

Meaning

id

Unique key

title

Product name

productUrl

Product link

imageUrl

Image URL

priceHtml

Discounted price (HTML)

originalPriceHtml

Original price (HTML)

showOriginalPrice

Show original price

saveTextHtml

"Save …" badge (HTML)

buttonText / isLoading / onAddToCart / reviews

Same idea as Upsell

savingsAmount, discountType, discountValue

Discount details (optional)

containerStyleVars, imageStyleVars, titleStyleVars, priceStyleVars, originalPriceStyleVars, savingsStyleVars, buttonStyleVars

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 if (!props.isPreview)

Call cart APIs yourself

Use priceHtml / saveTextHtml from props

Calculate your own money strings

Render {p.reviews} as-is

Put reviews inside dangerouslySetInnerHTML

Call p.onAddToCart()

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)

  1. Settings → Enable custom template editors → Save

  2. Cart editor → Upsells (or Upsell with Discount) → Custom template

  3. Paste a starter from Part 6 or Part 7

  4. CompileActiveSave

  5. Test in preview, then on the live cart

  6. Customize styles one step at a time

That is the full flow end to end.

Did this answer your question?