---
name: convention-shopify
type: convention
description: "Conventions for Shopify in Aled's system — store in scope, the connector-vs-theme split, product and collection naming, discounts and inventory, the ShopifyQL queries that define each KPI, and what requires human action. Read before any operation that touches the madebyneud store or its analytics. Follows the linear/github/vercel/sanity-conventions pattern; grown as the conventions solidify."
---

# convention-shopify

How Shopify is used in Aled's system. Follows the same pattern as `linear-conventions` and `github-conventions`. This is a working draft, not the final word.

## Store in scope

| Store | Detail |
| -- | -- |
| **Made by — Neud** | `www.madebyneud.com` · `5bcb56-07.myshopify.com` · GBP · United Kingdom · plan: Shopify |

A UK homeware/ceramics brand (vintage-white tableware) at the 0→1 stage. Other stores are added here as they come online.

## The connector-vs-theme split

Two surfaces, kept distinct (matches the `mbn.website` project note):

- **Store operations** — products, collections, orders, inventory, customers, discounts, analytics — run through the **Shopify store-management connector** (Cowork). No repo.
- **Storefront theme** — Liquid, sections, snippets, templates, assets — lives in the GitHub repo **`assoc-one/mbn-theme`** and is built by Forge, reviewed by Prism. Pixel designs and specifies it; never edits it through the connector.

Never do theme work through the connector, or store-data work through the repo.

## Naming

- **Products** — `Product name | Variant descriptor` (e.g. `Dinner Plate | Vintage White`). Title-case; the variant axis after the pipe.
- **Collections** — plain title-case nouns (e.g. `Tableware`, `Vintage White`). Handle follows from the title.

## Discounts and inventory

- Discounts are created via the connector; any code that spends margin is staged for the operator, not launched silently (see human-action boundary).
- Inventory is set at the location level; never zero-out or overwrite stock without confirming the location and current level first.

## KPI query definitions

So Atlas, Reach and `skill-campaign-report` all measure the same thing, each KPI maps to one canonical ShopifyQL query. Baseline captured 2026-06-19 (90-day window).

| KPI | ShopifyQL |
| -- | -- |
| Revenue | `FROM sales SHOW total_sales TIMESERIES month SINCE -365d UNTIL today` |
| Orders & AOV | `FROM sales SHOW orders, average_order_value SINCE -90d UNTIL today` |
| Sessions & conversion | `FROM sessions SHOW sessions, sessions_that_completed_checkout, conversion_rate SINCE -90d UNTIL today` |
| Repeat-customer rate | `FROM sales SHOW customers, returning_customers, returning_customer_rate SINCE -90d UNTIL today` |
| Top products | `FROM sales SHOW gross_sales, net_sales, orders GROUP BY product_title ORDER BY gross_sales DESC SINCE -90d UNTIL today` |
| Channel mix | `FROM sales SHOW orders, total_sales GROUP BY order_referrer_source SINCE -90d UNTIL today` |

Always report against the baseline and the KPI target, never as a bare number.

## Human-action boundary

The operator (not an agent) does anything that spends money, is irreversible, or is legally/financially binding: changing prices, issuing refunds, launching paid promotions, sending to a full email list, and **going live** (publishing the theme, removing password protection). Agents prepare and stage these; the operator releases them.

## Gotchas

- ShopifyQL needs `FROM … SHOW …`; use `TIMESERIES` for trends, `GROUP BY` for cuts.
- `total_sales` is net of discounts and returns; `gross_sales` is not — be explicit which a report uses.
- For resources without a built-in connector tool (metafields, pages, markets, gift cards), use the GraphQL Admin API tools rather than reporting the data as unavailable.
