> ## Documentation Index
> Fetch the complete documentation index at: https://codebeans-wishlyst.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Custom page templates

> Replace the wishlist page design with your own EJS markup, per market.

The wishlist page is built from three templates. You can override any of them with your own markup from the app, without touching the theme.

**Where:** **Manage Wishlist → Wishlist Page → Custom page template**

| Slot | Renders | Used on |
| - | - | - |
| **Wishlist Page** (`wishlist_page`) | Page header, tabs and content region | Listing and detail views |
| **Wishlist card** (`wishlist_card`) | One row per wishlist | My wishlists, Shared with me, Shared by me |
| **Product card** (`wishlist_item`) | One row per product | Wishlist detail view |

A slot without a custom template uses the built-in design. That way your store picks up future improvements to every slot you have not changed.

![Where each template slot is used on the page](https://mintcdn.com/codebeans-wishlyst/FOg6WqIIKdNxdSzd/images/mockups/template-slots.png?fit=max\&auto=format\&n=FOg6WqIIKdNxdSzd\&q=85\&s=85453b7bff170f4660264b6a5d0b76bb)

Where each template slot is used on the page

## Workflow

**1. Choose the fields you need**

Under **Default settings → Fields to fetch**, tick any extra product data your templates use (SKU, vendor, tags, metafields, …). The app fetches only what you tick, which keeps the page fast.

**2. Edit a slot**

Open a slot, switch to **Code**, and edit. The **Available variables** panel lists everything in the context. Use **Preview** to check the listing and detail views with sample data.

**3. Pick a market (optional)**

Under **Market**, keep **— Default —** to change the template for all markets, or pick a market to give it its own version. A market with no override uses the default.

**4. Save draft, then Publish**

**Save draft** keeps your work private. **Publish** makes it live. Use **Restore a previous version** to roll back.

> [!WARNING]
>
> If a published template breaks your live page, open **Online Store → Themes → Customize → App embeds → Wishlist Engine** and tick **Turn OFF custom wishlist templates on this theme**. That theme then uses the built-in design until you untick it.

## Syntax

Templates use [EJS](https://ejs.co/):

| Tag | Meaning |
| - | - |
| `<%= value %>` | Output, HTML-escaped. `null` and `undefined` print nothing. |
| `<%- html %>` | Output raw HTML. Only use it for values you trust. |
| `<% code %>` | Run JavaScript (loops, conditions) |
| `<%# comment %>` | Comment |

`include` is not supported.

```ejs Wishlist card theme={null}
<div class="wishlist_block" data-cb-wishlist-card
     <% if (wishlist.id) { %>data-cb-wishlist-id="<%= wishlist.id %>"<% } %>>
  <h4><%= wishlist.name || t('untitledWishlist', 'Untitled') %></h4>
  <p><%= wishlist.itemCount %> <%= wishlist.itemLabel %></p>

  <% wishlist.badges.forEach(function (badge) { %>
    <span class="badge" data-badge-type="<%= badge.type %>"><%= badge.label %></span>
  <% }) %>

  <button type="button" data-cb-action="open">View</button>
</div>
```

```ejs Product card theme={null}
<div class="my-item" data-cb-item data-cb-item-id="<%= item.id %>" data-cb-variant-id="<%= item.variantId %>">
  <a href="<%= item.productUrl %>">
    <img src="<%= helpers.imageUrl(item, 400) %>" alt="<%= item.title %>">
  </a>
  <h5><%= helpers.truncate(item.title, 60) %></h5>
  <p><%= helpers.formatPrice(item.price, item.currency) %></p>

  <% if (item.availableForSale) { %>
    <button type="button" data-cb-action="add-to-cart">Add to cart</button>
  <% } else { %>
    <span>Sold out</span>
  <% } %>
  <button type="button" data-cb-action="delete-item">Remove</button>
</div>
```

If a template throws an error on the storefront, only that region falls back to the built-in design. The error goes to the browser console.

## Context

All variables are read-only.

**tab** `string`

`my`, `shared_with_me` or `shared_by_me`

**view** `string`

`listing` or `detail`

**shop** `object`

`{ domain, name, currency, moneyFormat }`

**market** `object`

`{ handle, id, isPrimary, country, language }`

**customer** `object`

`{ id, firstName, lastName, fullName, isGuest }`. The email is left out on purpose.

**settings** `object`

Widget settings for the current language

**t(key, fallback)** `function`

Returns the translated text for a key, or the fallback.

**plan** `object`

`{ wishlistSharing, shareViaEmail }`. Use it to hide features the store's plan does not include.

**viewer** `object`

`{ role, isOwner, canEdit, canMove, canShare, canSave, canManageShare, canGoBack }`. `role` is `owner`, `sharer` or `recipient`.

**pagination** `object`

`{ page, limit, totalItems, hasNextPage, nextPage }`

**tabs** `array`

`[{ key, label, current, shared }]`

**helpers** `object`

**helpers**

**formatPrice(amount, currency)** `function`

Formats with the shop's money format

**imageUrl(itemOrUrl, size)** `function`

Sized Shopify CDN image URL

**truncate(value, n)** `function`

Cuts text to `n` characters

**date(value)** `function`

Formats a date

**wishlists / wishlist** `array / object`

Listing view and Wishlist card.

**wishlist**

**id** `string`

**publicId** `string`

**shareId** `string | null`

`null` on the My wishlists tab

**name** `string`

**isPublic** `boolean`

**itemCount** `number`

**itemLabel** `string`

"item" or "items", translated

**previewItems** `array`

First few items, for thumbnails

**moreCount** `number`

Items beyond the preview

**badges** `array`

`[{ type, label, tone, details }]`

**createdAt** `string`

**sharedBy** `object`

`{ name, email, isGuest }`, Shared with me only

**sharedWith** `object`

Shared by me only

**items / item** `array / object`

Detail view and Product card.

**item**

**id** `string`

**productId** `string`

**productHandle** `string`

**variantId** `string`

**variantTitle** `string`

**title** `string`

**image** `string`

**price** `number`

**currency** `string`

**availableForSale** `boolean`

**productUrl** `string`

**maxQty** `number`

**createdAt** `string`

When the shopper saved the item

**metafields** `object`

Always an object, filled with the metafields you selected

Fields you tick under **Fields to fetch** are added too: `sku`, `compareAtPrice`, `vendor`, `productType`, `tags`, `selectedOptions`, `inventoryQuantity`, `priceRangeMax`, `description`, `seoTitle`, `seoDescription`, `collectionTitles`, `collectionHandles`, `collectionIds`, `images`, `publishedAt`, `productCreatedAt`, `totalInventory`, `category`, `status`.

**share** `object`

Shared tabs only: `{ id, recipientName, recipientEmail, status, savedToWishlistName, isRevoked, isLinkShare }`

**banner** `object`

`{ text, tone }`, a notice the page wants to show

## Required hooks

The app finds the parts of your markup through `data-cb-*` attributes. Your template must include the required ones, or the page cannot work.

> [tab] Wishlist Page
>
> **Required:** `data-cb-tabs`, `data-cb-region="<%= view %>"`, `data-cb-list` (the direct parent of the rows)
>
> **Optional:** `data-cb-empty`, `data-cb-loadmore`, `data-cb-page-heading`, `data-cb-tab="<key>"`, `data-cb-tab-count="<key>"`, `data-cb-detail-header`, `data-cb-toggle="public"`, `data-cb-public-label`, `data-cb-slot="badges|item-count-badge|products"`
>
> Add the class `cb-shared-tab` to the shared tabs so the page can hide them when sharing is off.

> [tab] Wishlist card
>
> **Required:** `data-cb-wishlist-card`, and an element with `data-cb-action="open"`
>
> **Optional:** `data-cb-wishlist-id`, `data-cb-wishlist-public-id`, `data-cb-share-id`

> [tab] Product card
>
> **Required:** `data-cb-item`, `data-cb-item-id`, `data-cb-variant-id`
>
> **Optional:** `data-cb-product-id`, `data-cb-price`, `data-cb-currency`, `data-cb-available`, `data-cb-qty` (quantity input), `data-cb-price-display` with `data-cb-base-price`, `data-cb-item-title`, `data-cb-move-select` (a `<select>` the app fills with the shopper's other wishlists)

## Actions

Put `data-cb-action` on any clickable element. You do not need to write event handlers.

| Action | What it does | Shown only when |
| - | - | - |
| `open` | Opens a wishlist | — |
| `back` | Back to the list | — |
| `create-list` | Opens "Create a new wishlist" | — |
| `rename` | Opens the rename dialog | `viewer.canEdit` |
| `delete` | Deletes the wishlist | `viewer.canEdit` |
| `delete-item` | Removes the product | `viewer.canEdit` |
| `share` | Opens the share dialog | `viewer.canShare` |
| `update-share` | Changes who a share is with | `viewer.canManageShare` |
| `stop-share` | Stops sharing | `viewer.canManageShare` |
| `save-to-my-wishlist` | Copies a shared wishlist to the shopper's own wishlists | `viewer.canSave` |
| `add-to-cart` | Adds the product to the cart | — |

When the viewer lacks the permission, the app hides the element and removes its action, so a shared viewer can never delete someone else's items.

## Custom CSS

**Default settings → Custom CSS** is a stylesheet that loads after the app's own CSS. It is published with your templates.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.