# Developer overview

> What a theme developer can change in the AndOr storefront widgets, and where the widgets get their data.

Source: https://backtrackhq.com/docs/developers/overview/

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](https://backtrackhq.com/docs/get-started/promotions-and-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](https://backtrackhq.com/docs/developers/app-embed-and-blocks/) |
| Add a widget where the theme has no block slot | One of the seven one-line tags | [One-line tags](https://backtrackhq.com/docs/developers/one-line-tags/) |
| Show a badge or sale price on product cards | Pasted Liquid recipes | [Product card recipes](https://backtrackhq.com/docs/developers/product-card-recipes/) |
| Change colors, corners, spacing | `--andor-*` CSS custom properties | [Styling tokens](https://backtrackhq.com/docs/developers/tokens/) |
| Pick a ready-made look | The embed's **Style preset** | [Style presets](https://backtrackhq.com/docs/developers/presets/) |
| Style a state (reached, sold out, applied) | `data-*` attributes and state classes | [Data attributes and states](https://backtrackhq.com/docs/developers/data-attributes/) |
| React when a widget changes | `andor:` DOM events and pixel events | [Events](https://backtrackhq.com/docs/developers/events/) |
| Redraw after an Ajax swap | `refresh()` on the public objects | [JavaScript API](https://backtrackhq.com/docs/developers/javascript-api/) |
| Know how upsell adds reach the cart | The cart update paths | [Cart updates](https://backtrackhq.com/docs/developers/cart-updates/) |
| Tie a widget you built yourself to an offer | The `_andor_offer_id` line property | [Measure your own widget](https://backtrackhq.com/docs/developers/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 `.price` or `.badge` does 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:

```liquid
{%- 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:

```liquid
{%- 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
  `discounts` and the shop's `shop_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 bare `gift_card` is 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.js` and the `/cart/*.js` endpoints, 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

1. **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.
2. **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](https://backtrackhq.com/docs/get-started/install/#what-needs-the-app-embed).

If a widget still does not show, see
[Widgets not showing](https://backtrackhq.com/docs/help/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.
