Developer overview
On this page
AndOr draws its storefront widgets with one theme app extension: an app embed called AndOr Discount Display and nine app blocks. This section is for theme developers who want to place, restyle or listen to those widgets.
The widgets show promotions and upsell offers. For how the two fit together, see Promotions and upsell offers.
Note Most merchants never need code. The app blocks and the app embed are set up in the theme editor. Code is for themes with no slot for a block, for product cards, and for custom styling.
What you can change, and how
| You want to | Use | Page |
|---|---|---|
| Add a widget in the theme editor | App blocks and the app embed | App embed and blocks |
| Add a widget where the theme has no block slot | One of the seven one-line tags | One-line tags |
| Show a badge or sale price on product cards | Pasted Liquid recipes | Product card recipes |
| Change colors, corners, spacing | --andor-* CSS custom properties |
Styling tokens |
| Pick a ready-made look | The embed’s Style preset | Style presets |
| Style a state (reached, sold out, applied) | data-* attributes and state classes |
Data attributes and states |
| React when a widget changes | andor: DOM events and pixel events |
Events |
| Redraw after an Ajax swap | refresh() on the public objects |
JavaScript API |
| Know how upsell adds reach the cart | The cart update paths | Cart updates |
| Tie a widget you built yourself to an offer | The _andor_offer_id line property |
Measure your own widget |
How the widgets render
The widgets render in the light DOM, from Liquid, on the server. There is no shadow root.
- Classes are the styling surface. Every class starts with
andor-, so a theme rule like.priceor.badgedoes not reach into the widget. There is no::part. - Liquid picks the discount. For the sale price, the savings badge, the volume table, the collection banner and the discount callout, the final text is in the HTML the server sent.
- Some widgets are printed by JavaScript. The countdown, the offers list, the goal bar, the volume picker layouts, the offer picker and the cart claim rows. Liquid still decides which discount applies and prints a skeleton at the finished size. The script only turns those facts into words. It never picks a discount of its own.
- JavaScript off still tells the truth. The first paint shows the right price, badge and volume table. A widget printed by JavaScript keeps its space and shows nothing. For example, with scripts blocked a goal bar is an empty box, never a wrong “Spend $12.00 more”.
What the widgets read
The widgets read JSON metafields in the app-reserved namespace
$app:andor_display. The main keys:
| Owner | Key | What it holds |
|---|---|---|
| product | discounts |
deals that target this product |
| collection | discounts |
deals that target this whole collection |
| shop | shop_discounts |
storewide deals |
| shop | offers |
the whole catalog’s live deals |
| product, collection and shop | best_percent |
the best percentage |
| product, collection and shop | best_amount_each |
the best per-item amount |
| product, collection and shop | best_ends_at |
epoch seconds when the two above stop being true |
Inside an app block that belongs to AndOr, the namespace resolves directly:
{%- assign andor_none = '' | split: ',' -%}
{%- assign andor_rows = product.metafields["$app:andor_display"].discounts.value | default: andor_none -%}In your own theme code, $app: does not resolve. Use the rendered form,
where <app-id> is the app’s numeric id:
{%- assign andor_rows = product.metafields.app--<app-id>--andor_display.discounts.value -%}Tip You do not need to find the app id. The app’s Storefront blocks page renders every recipe with your store’s namespace already filled in. Open Storefront display, then choose See all blocks from the page menu.
Rules that matter when you read the metafields yourself:
- Combine both owners. A product’s best deal is the better of its own
discountsand the shop’sshop_discounts. Storewide deals are not copied onto every product. - Test
product.gift_card?. Shopify does not discount gift cards, but the shortcut numbers are still written on them. Write the question mark. The baregift_cardis nil and fails silently. - A missing key means “nothing”. The app deletes a shortcut key when nothing qualifies. It never writes a zero.
- Only discounts the merchant turned on are there. A discount that is not switched on in Storefront display is not in the metafields at all.
Network calls
Every call the widgets make goes to the store’s own domain. There are no third-party requests.
/cart.jsand the/cart/*.jsendpoints, to read and change the cart.?sections=, to redraw Dawn’s cart drawer after an upsell add.- Upsell offer counts (views, clicks and adds), sent through your store’s app proxy, only after the shopper’s consent.
- Error reports from AndOr’s own files, sent through your store’s app proxy. They carry no cart, customer or URL data.
Two things to check first
- The discount is switched on in the app. In AndOr, open Storefront display and turn on Show on storefront for the discount. Every widget needs this.
- The app embed is on. In the theme editor, open App embeds and switch on AndOr Discount Display. It prints the token layer and loads the shared scripts. Blocks show without it. One-line tags, the cart drawer, the shared look settings, text overrides, translations and the DOM events all need it. See What needs the app embed.
If a widget still does not show, see Widgets not showing.
Debugging
The app embed has a setting Log AndOr display decisions to the browser console. Turn it on while you troubleshoot, and off again after.
See it in action