Wayfinder: chart the Shopify headless commerce map
The route from "mozimo.in displays Shopify products" to "mozimo.in sells them", as a plan with a phase spine and gates, plus the open decision tickets that still shape it and the evidence they rest on. - .scratch/shopify-headless-commerce/: plan.md (phases and gates), map.md (the wayfinder map), 10 open decision tickets - CONTEXT.md: Storefront / Theme store / Cart / Parity vocabulary - docs/adr/0001-0003: Checkout stays Shopify-hosted, one Variant per product, the checkout domain stays shop.mozimo.in - docs/research/ and docs/shopify-catalog-inventory.md: verified store facts, the Cart API probe, and the catalogue inventory Raw Shopify doc scrapes under .scratch/shopify-research/ are ignored: they are working material, not record.
This commit is contained in:
1 parent
bb0c78fec5
commit
de0553d0d4
21 files changed
+3483
No files matched your search
@@ -46,3 +46,6 @@ next-env.d.ts
|
||||
|
||||
# minted shopify token cache
|
||||
/.cache/
|
||||
|
||||
# wayfinder research scratch — raw scrapes, deliberately not in history
|
||||
/.scratch/shopify-research/
|
||||
@@ -0,0 +1,44 @@
|
||||
# Seed the dev store from the live catalogue
|
||||
|
||||
`Type:` task (HITL)
|
||||
`Status:` open
|
||||
|
||||
Part of [the map](../map.md). Empty the frontier from the top.
|
||||
|
||||
## Question
|
||||
|
||||
The dev store `staging-iw0gviik.myshopify.com` is empty. How does it come to hold the live catalogue faithfully, so that Phase 1 Cart work is built against realistic data?
|
||||
|
||||
The manual CSV route was chosen for this. Verification has now shown exactly where it holds and where it **cannot**:
|
||||
|
||||
### ✅ Products, and product metafields with preparation
|
||||
|
||||
- Export: **Products → Export** → scope → *Plain CSV* or *CSV for Excel/Numbers* → **Export products**. One file. Note that an **All products export is emailed**, not downloaded.
|
||||
- Import: **Products → Import** → add file → *Overwrite products with matching handles* → **Import products**.
|
||||
- Product metafields **do** ride in the CSV as their own columns, named `<Name> (product.metafields.<namespace>.<key>)`. `custom.ingredients` as multi-line text and `custom.energy_kcal` as a number are both documented-supported types.
|
||||
- **Precondition:** the metafield **definitions must already exist in the destination store** (Settings → Custom data → Products). Shopify's docs only promise columns for *defined* metafields. What happens to an undefined metafield's value is undocumented — so create the definitions before importing, not after.
|
||||
- Hazards: with *Overwrite* on, a **blank metafield cell overwrites the destination value as blank**. Images are URLs only and Shopify re-hosts them at import, so production's products and images must stay publicly reachable until the import finishes. There is **no dry run**, imports cannot be cancelled, there is no import history, and the file cap is 15 MB.
|
||||
|
||||
### ❌ Collection membership — impossible by CSV
|
||||
|
||||
The `Collection` column is **never exported**, and on import it is the only column you may add — and it admits **one collection per product**. Most of this catalogue belongs to three or four collections each: `mozimo-carnival-celebration-m` is in `best-sellers`, `gift-collection`, `diwali` and `hampers-boxes` at once. Sixteen collections and their multi-membership therefore need a **second mechanism**.
|
||||
|
||||
This part re-opens the seeding decision, because the chosen route cannot finish the job alone.
|
||||
|
||||
### ⚠️ Variant option shape — read the real export
|
||||
|
||||
Shopify's docs say a one-option product should carry `Default Title` in `Option1 value`, while the **official sample CSV represents its single-option product as `Option1 name = Title`, `Option1 value = Premium`** — which is exactly this catalogue's stored shape. The docs and their own sample disagree, and the documented hazards around *changing* option values (variants are deleted and recreated, so IDs change) make this worth confirming rather than assuming. Exporting from production settles it, and that export is needed anyway.
|
||||
|
||||
## To resolve
|
||||
|
||||
1. Export the full catalogue from production and record what `Option1 name` / `Option1 value` actually contain.
|
||||
2. Create the metafield definitions in the dev store before importing.
|
||||
3. Run a **small-batch import with *Overwrite* off** to surface per-line errors cheaply, then the full set.
|
||||
4. Decide the second mechanism for collection membership — admin bulk-editor passes, or a small Admin API script using the credentials the repo already mints. Record which was chosen and why.
|
||||
5. Record what actually landed: product count, collection count and membership, metafield coverage, anything rebuilt by hand.
|
||||
|
||||
Facts later tickets depend on: which products exist in the dev store, whether their Variant GIDs differ from production's (they will), and whether the metafield values survived.
|
||||
|
||||
## Answer
|
||||
|
||||
<!-- filled on resolution -->
|
||||
@@ -0,0 +1,19 @@
|
||||
# Cart persistence and SSR hydration
|
||||
|
||||
`Type:` grilling (HITL)
|
||||
`Status:` open
|
||||
`Blocked by:` 01
|
||||
|
||||
Part of [the map](../map.md).
|
||||
|
||||
## Question
|
||||
|
||||
How is a Cart identified and carried across requests? Shopify addresses a Cart by ID, and this Storefront renders on the server, so the mechanism has to survive both.
|
||||
|
||||
Decide: where the Cart ID lives (an httpOnly cookie the server sets, client storage, or both), what its attributes are, and how the client hydrates without a flash of an empty Cart.
|
||||
|
||||
Decide the failure paths too, because both are reachable: a Cart expires after 30 days, and Shopify deletes it on checkout completion — so a returning buyer can arrive holding a cookie that points at nothing. What happens then — silent re-create, cleared Cart, or an error?
|
||||
|
||||
## Answer
|
||||
|
||||
<!-- filled on resolution -->
|
||||
@@ -0,0 +1,22 @@
|
||||
# Degraded-mode experience
|
||||
|
||||
`Type:` grilling (HITL)
|
||||
`Status:` open
|
||||
|
||||
Part of [the map](../map.md).
|
||||
|
||||
## Question
|
||||
|
||||
When Shopify is unreachable, what does a buyer actually get?
|
||||
|
||||
The Storefront currently falls back to the Editorial catalog, which is display-only and whose prices can drift from commerce truth. We decided that stale **real** data beats fabricated data, with buying disabled. This ticket settles what that means concretely:
|
||||
|
||||
- When is degraded mode entered, and how is it detected — a failed Cart call, a failed product fetch, or both?
|
||||
- What does a buyer see? Does a notice appear, what does it say, and is it brand-appropriate for a luxury storefront?
|
||||
- Is stale data served transparently, or is the page visibly marked as out of date?
|
||||
- What exactly is disabled — the add-to-cart control, the Cart itself, or the route to Checkout?
|
||||
- What happens to an existing Cart mid-session when the API drops?
|
||||
|
||||
## Answer
|
||||
|
||||
<!-- filled on resolution -->
|
||||
@@ -0,0 +1,25 @@
|
||||
# Prototype the Cart surfaces
|
||||
|
||||
`Type:` prototype (HITL)
|
||||
`Status:` open
|
||||
`Blocked by:` 02
|
||||
|
||||
Part of [the map](../map.md).
|
||||
|
||||
## Question
|
||||
|
||||
What do the mini-cart drawer and the `/cart` page actually look like, and what do they do?
|
||||
|
||||
Raise the fidelity of the discussion cheaply: a rough, concrete prototype of both surfaces is worth more than further prose. React to it and settle:
|
||||
|
||||
- What the drawer shows on add-to-cart — the added line only, or the whole Cart?
|
||||
- Line-item controls: quantity, remove, and how the gifting note attaches.
|
||||
- Where subtotal, discount entry and any shipping-threshold messaging sit.
|
||||
- How the drawer and the full page relate — does one open the other?
|
||||
- The transition to Checkout: what the button says, and how the handoff to `shop.mozimo.in` is framed so it doesn't read as leaving the brand.
|
||||
|
||||
The design language is editorial and restrained; the Cart is the first transactional surface in an otherwise editorial site, which is the tension worth prototyping.
|
||||
|
||||
## Answer
|
||||
|
||||
<!-- filled on resolution -->
|
||||
@@ -0,0 +1,22 @@
|
||||
# The gifting model
|
||||
|
||||
`Type:` grilling (HITL)
|
||||
`Status:` open
|
||||
|
||||
Part of [the map](../map.md).
|
||||
|
||||
## Question
|
||||
|
||||
What is the gifting model on the Storefront?
|
||||
|
||||
A gift note as a Cart attribute is in Phase 1, and wrap and delivery options are Phase 3. Settle:
|
||||
|
||||
- Which of note, wrap and scheduled delivery actually apply to how this brand sells.
|
||||
- Where each is entered — product page, drawer, or `/cart`.
|
||||
- Whether it is per-Cart or per-line. A hamper and a bar in one order may need different messages.
|
||||
- Whether wrap or scheduled delivery carry a charge, and how that appears in the Cart.
|
||||
- What the Cart records versus what Checkout renders. Cart attributes flow through to the order; scheduled delivery in particular tends to need Shopify-side configuration, so establish what is even possible before promising it.
|
||||
|
||||
## Answer
|
||||
|
||||
<!-- filled on resolution -->
|
||||
@@ -0,0 +1,20 @@
|
||||
# Discount configuration check
|
||||
|
||||
`Type:` task
|
||||
`Status:` open
|
||||
|
||||
Part of [the map](../map.md).
|
||||
|
||||
## Question
|
||||
|
||||
Does this store run any discounts?
|
||||
|
||||
The Storefront API cannot answer this: `discountCodes` and `discountNodes` are not fields on `QueryRoot` at 2026-10, and discount data is only reachable per live Cart. Whether a discount exists is therefore unverifiable from code, and the parity checklist cannot be written without knowing.
|
||||
|
||||
Resolve by checking Shopify admin directly: are there active discount codes, automatic discounts, or abandoned-checkout style offers? Do any apply by default at checkout?
|
||||
|
||||
Record what exists — codes, types, conditions, expiry — and note which of them a headless Cart would need to reproduce (`cartDiscountCodesUpdate` applies a code, but automatic discounts are Shopify-side).
|
||||
|
||||
## Answer
|
||||
|
||||
<!-- filled on resolution -->
|
||||
@@ -0,0 +1,28 @@
|
||||
# The Parity checklist
|
||||
|
||||
`Type:` grilling (HITL)
|
||||
`Status:` open
|
||||
`Blocked by:` 05, 06
|
||||
|
||||
Part of [the map](../map.md).
|
||||
|
||||
## Question
|
||||
|
||||
What exactly must the Storefront be able to do before the Theme store's pages are retired?
|
||||
|
||||
Phase 5's gate is "the parity checklist is met", which is circular until the checklist exists. Enumerate it and settle what is genuinely required versus merely nice:
|
||||
|
||||
- Guest checkout, and confirmation that no account is ever needed to buy.
|
||||
- Discount codes, per whatever the configuration check found.
|
||||
- Gifting, per the gifting model.
|
||||
- Shipping and delivery options as a buyer experiences them. India-only is settled; what is shown about it is not.
|
||||
- Order confirmation and transactional email, which Shopify sends but which currently carries the Theme store's branding.
|
||||
- Order history, which depends on Phase 4 — so decide whether Phase 4 gates this or merely improves it.
|
||||
- Search, and whether the existing predictive search is sufficient.
|
||||
- Anything else a buyer can currently do on `shop.mozimo.in` that mozimo.in cannot.
|
||||
|
||||
For each item, mark whether it gates retirement or is deferred. Anything that gates retirement has to be finished before Phase 5, so this checklist sets the real scope of Phase 3.
|
||||
|
||||
## Answer
|
||||
|
||||
<!-- filled on resolution -->
|
||||
@@ -0,0 +1,23 @@
|
||||
# Audit what points at the Theme store
|
||||
|
||||
`Type:` task (HITL)
|
||||
`Status:` open
|
||||
|
||||
Part of [the map](../map.md).
|
||||
|
||||
## Question
|
||||
|
||||
What currently sends traffic to `shop.mozimo.in`?
|
||||
|
||||
This cannot be answered from the codebase alone, and finding a live campaign pointed at a Theme store URL two days before cutover is the classic way this migration goes wrong. Inventory:
|
||||
|
||||
- **In the repo** — every hardcoded `shop.mozimo.in` reference, including the header's account and Bag icons, and any product URL built from the Shopify domain. (The audit should cover this, but the repo half is discoverable now.)
|
||||
- **Outside the repo** — paid ad campaigns and their landing URLs, Google and Meta shopping feeds, Instagram and Facebook shop, marketplace listings, email templates, and printed packaging or QR codes.
|
||||
|
||||
For each: what it points at, whether a mozimo.in equivalent exists, and whether it needs updating before or after cutover.
|
||||
|
||||
This gates Phase 5 and sets how much redirect coverage is actually required.
|
||||
|
||||
## Answer
|
||||
|
||||
<!-- filled on resolution -->
|
||||
@@ -0,0 +1,22 @@
|
||||
# Catalogue content gaps
|
||||
|
||||
`Type:` grilling (HITL)
|
||||
`Status:` open
|
||||
|
||||
Part of [the map](../map.md).
|
||||
|
||||
## Question
|
||||
|
||||
Once the full 61-product catalogue is surfaced, most product pages will render without the content the design assumes: 41 of 61 products have no ingredients and no nutrition metafields, and `custom.tagline` is populated on exactly one product.
|
||||
|
||||
Settle how that is handled:
|
||||
|
||||
- Does the Storefront absorb it with conditional rendering, and what fills the space left behind? A product page with a missing panel can look broken rather than sparse.
|
||||
- Or is the data populated in Shopify first, which is content work rather than engineering work?
|
||||
- Or do the products without content stay unsurfaced until content exists, which contradicts surfacing the full catalogue but keeps every page complete?
|
||||
|
||||
Also settle the dead compatibility path this exposes: the entire `facts.*` metafield namespace is empty, so the `facts.nutrition` / `facts.ingredients` / `facts.storage` branch in `shapeProduct` can never fire and should be removed or justified.
|
||||
|
||||
## Answer
|
||||
|
||||
<!-- filled on resolution -->
|
||||
@@ -0,0 +1,23 @@
|
||||
# Confirm the customer accounts version
|
||||
|
||||
`Type:` task
|
||||
`Status:` open
|
||||
|
||||
Part of [the map](../map.md).
|
||||
|
||||
## Question
|
||||
|
||||
Is this store on new customer accounts or legacy (classic) customer accounts?
|
||||
|
||||
Everything points to **new**: the admin panel exposes **Self-serve returns** and **Store credit** (✔ for new accounts, ✘ for legacy in Shopify's own comparison table), and the account URL is the `shopify.com/<id>/account` form that only the new system uses.
|
||||
|
||||
But that URL is **not conclusive**. Shopify's upgrade guide populates the new URL *before* cutover, so a store mid-migration can show it while still serving legacy account pages. Legacy accounts were deprecated on 2026-02-26 and the **sunset date is still unannounced** as of October 2026 — so if this store is genuinely still on legacy, Phase 4 stops being a comfortable late phase and has to move ahead of the sunset.
|
||||
|
||||
There are two clean discriminators, either of which settles it:
|
||||
|
||||
- The **Upgrade banner** on Settings → Customer accounts.
|
||||
- Admin GraphQL `Shop.customerAccountsV2.customerAccountsVersion`, which returns `CLASSIC` or `NEW_CUSTOMER_ACCOUNTS`.
|
||||
|
||||
## Answer
|
||||
|
||||
<!-- filled on resolution -->
|
||||
@@ -0,0 +1,43 @@
|
||||
# Map — Shopify Headless Commerce
|
||||
|
||||
The wayfinder map for moving mozimo.in from *displaying* Shopify products to *selling* them.
|
||||
|
||||
The destination, the phase spine and its gates live in [`plan.md`](./plan.md). The evidence behind them lives in [`docs/research/`](../../docs/research/) and [`docs/shopify-catalog-inventory.md`](../../docs/shopify-catalog-inventory.md).
|
||||
|
||||
## Destination
|
||||
|
||||
A Storefront on which a buyer can assemble a Cart, review it, and complete a purchase — with `shop.mozimo.in` reduced to the Checkout domain and its storefront pages retired.
|
||||
|
||||
## Notes
|
||||
|
||||
**Vocabulary:** [`CONTEXT.md`](../../CONTEXT.md). Use **Storefront**, **Theme store**, **Cart** and **Checkout** as defined there; the Cart/Bag and Storefront/Theme-store distinctions matter in every ticket.
|
||||
|
||||
**Skills for every session:** `grilling` and `domain-modeling`. For Shopify API shapes, the official skill bundle is installed at `.agents/skills/shopify-storefront-graphql/` but is **not registered** with the harness — `skill` will not load it. Its `scripts/search_docs.js` works when run directly and is the fastest way to check a mutation's real field names; its `assets/` schema snapshot is `2026-01`.
|
||||
|
||||
**Decisions settled before this map was charted** — the destination, the spine and gates, the verified store constraints, and what is out of scope — are recorded in [`plan.md`](./plan.md), not here. This map tracks what is still open.
|
||||
|
||||
**Shopify docs method:** appending `.md` to a `shopify.dev` URL returns the untruncated raw Markdown, and the same trick on `help.shopify.com` — with an `/en/` locale prefix — bypasses its 403 bot challenge. Use those rather than reading HTML.
|
||||
|
||||
**Standing preference:** plan, don't build. Tickets resolve decisions; implementation is a later, separately scoped effort per phase.
|
||||
|
||||
## Decisions so far
|
||||
|
||||
<!-- one line per closed ticket, enough to judge relevance, then the link -->
|
||||
|
||||
## Not yet specified
|
||||
|
||||
- Whether the Storefront needs a returns or refunds surface of its own. The store's self-serve returns toggle is off, so this may stay a policy page.
|
||||
- What transactional email looks like once buying moves: Shopify sends order confirmations, but they currently carry the Theme store's branding.
|
||||
- Where the 46 products absent from the Editorial catalog should sit in the Storefront's merchandising, once the full catalogue is surfaced.
|
||||
- Whether the 16 live collections need a new URL shape on the Storefront, and what that costs in SEO.
|
||||
- Cart edge cases beyond the documented limits: what a buyer sees when a Cart expires mid-session, and how quantity ceilings are communicated.
|
||||
- Post-purchase order tracking on mozimo.in, which may belong with Phase 4.
|
||||
|
||||
## Out of scope
|
||||
|
||||
- **Fully on-domain checkout.** Plus-only; revisit only via a plan upgrade. See [ADR-0001](../../docs/adr/0001-checkout-stays-shopify-hosted.md).
|
||||
- **Multi-currency and Shopify Markets.** One currency (INR), one country (IN).
|
||||
- **Subscriptions and selling plans.** No product requires one.
|
||||
- **Gift cards.** None exist in the store, and none are queryable on the Storefront API.
|
||||
- **Inventory and stock display.** Explicitly not tracked and not wanted; sold-out is not a reachable state.
|
||||
- **Customer account migration.** The store almost certainly uses new customer accounts — self-serve returns and store credit are both present, and the account URL is on `shopify.com`. A single admin check confirms it, and either way no migration is in scope.
|
||||
@@ -0,0 +1,138 @@
|
||||
# Shopify Headless Commerce — Plan
|
||||
|
||||
The phased route from "mozimo.in displays products from Shopify" to "mozimo.in sells
|
||||
them", and the retirement of the theme store's pages.
|
||||
|
||||
This plan holds the spine and the gates. The open decisions inside each phase are
|
||||
held as wayfinder tickets on `map.md` in this directory.
|
||||
|
||||
## Destination
|
||||
|
||||
A Storefront on which a buyer can assemble a Cart, review it, and complete a purchase —
|
||||
with `shop.mozimo.in` reduced to the Checkout domain and its storefront pages retired.
|
||||
|
||||
## Settled constraints
|
||||
|
||||
Facts, verified against the live store and Shopify's documentation in October 2026.
|
||||
Sources: [`docs/research/shopify-headless-commerce-2026-10.md`](../../docs/research/shopify-headless-commerce-2026-10.md),
|
||||
[`docs/research/shopify-cart-probe-2026-10.md`](../../docs/research/shopify-cart-probe-2026-10.md),
|
||||
[`docs/shopify-catalog-inventory.md`](../../docs/shopify-catalog-inventory.md).
|
||||
|
||||
| Constraint | Value |
|
||||
| --- | --- |
|
||||
| Production plan | **Grow** — checkout UI extensions and checkout branding are Plus-only, so custom checkout is unavailable by plan |
|
||||
| Storefront API version | Pinned in code as `2025-01`, a retired version currently served as `2025-10`. Target is **2026-10** |
|
||||
| Checkout | Shopify-hosted, reached via the Cart's `checkoutUrl`. The Storefront never renders it |
|
||||
| Checkout host | **`shop.mozimo.in`** — store-level config, not the host the API is called on |
|
||||
| Catalogue | **61 products**, each with **exactly one Variant**; sizes are separate products |
|
||||
| Currency / market | Single currency **INR**, single country **IN**. No Markets complexity |
|
||||
| Shipping | **India only.** `shipsToCountries` currently lists 29 destinations and disagrees with the published policy |
|
||||
| Inventory | **Not tracked, and not wanted.** `availableForSale` is `true` for all 61 products, permanently |
|
||||
| Gift cards | **None exist**, and none are queryable on the Storefront API |
|
||||
| Customer accounts | **New** customer accounts — strongly indicated by the admin panel, not certain. The legacy sunset is still unannounced, so verify rather than assume |
|
||||
| Carts | Not shared with the theme store, and cannot be — the Ajax API is theme-only |
|
||||
| Catalogue seeding | The product CSV round-trips products and product **metafields** (definitions must exist first), but **cannot** carry collection membership — the `Collection` column is import-only and permits one collection per product |
|
||||
| Cart limits | 500 lines per cart, 250 lines per request, expire after 30 days, deleted on checkout completion |
|
||||
|
||||
## Phase spine
|
||||
|
||||
Each phase ships independently. The gate is what makes it a phase rather than a task list.
|
||||
|
||||
### Phase 0 — Foundations
|
||||
|
||||
Prerequisites for anything that writes.
|
||||
|
||||
- Pin the Storefront API to `2026-10` and log `X-Shopify-API-Version` so drift is visible
|
||||
instead of silent. Remove the stale `2025-01` comment about `predictiveSearch`.
|
||||
- Carry the Variant GID and per-variant price in `ShopProduct` — an add-to-cart needs a
|
||||
merchandise ID and there is none today.
|
||||
- Send `Shopify-Storefront-Buyer-IP` on server-side calls.
|
||||
- Standardise the credential path on the minted OAuth token.
|
||||
- Shopify admin data fixes: shipping zones down to India only; populate `productType`
|
||||
and weight tags so the collection filter chips and card weights stop reading nothing.
|
||||
- Degraded mode: prefer stale cached real data over the Editorial catalog, and disable
|
||||
buying when the Cart API is unreachable.
|
||||
- Fix the broken `/products/rakhi-bark-box-200gm` link.
|
||||
|
||||
**Gate:** product and collection pages render as they do today, every product exposes a
|
||||
Variant GID, and the served API version is `2026-10`.
|
||||
|
||||
### Phase 1 — Cart
|
||||
|
||||
- Shopify Cart API: create, add, update, remove, query.
|
||||
- The Cart ID persisted in an httpOnly cookie, hydrated safely under SSR.
|
||||
- Mini-cart drawer and a `/cart` page, with editable lines.
|
||||
- Add-to-cart on the product page.
|
||||
- Gift note captured as a Cart attribute.
|
||||
- Analytics: `add_to_cart`, `view_cart`, `remove_from_cart`.
|
||||
|
||||
**Gate:** a guest can add, edit and remove; the subtotal is correct; the Cart survives a
|
||||
reload and a browser restart.
|
||||
|
||||
### Phase 2 — Checkout handoff
|
||||
|
||||
- Redirect to the Cart's `checkoutUrl`.
|
||||
- Fire `begin_checkout` with the real Cart.
|
||||
- Handle price change and Cart expiry at handoff. Sold-out is not a reachable state.
|
||||
- Verify an India address completes and a non-India address is refused.
|
||||
- Complete at least one test-mode order through the Bogus Gateway.
|
||||
|
||||
**Gate:** a genuine test-mode order completes end-to-end from mozimo.in.
|
||||
|
||||
### Phase 3 — Commercial parity
|
||||
|
||||
- Surface the full 61-product catalogue.
|
||||
- Discount code entry in the Cart. Whether any discount exists is unverifiable from the
|
||||
Storefront API, so this is built against configuration, not against observed data.
|
||||
- Gifting: wrap and delivery options beyond the Phase 1 gift note.
|
||||
- Quick-add from collection grids.
|
||||
- Close the content gaps that make product pages look broken at scale: 41 of 61 products
|
||||
have no ingredients or nutrition, and `custom.tagline` is populated on exactly one.
|
||||
- Rebuild collection pages against the 16 live collections.
|
||||
|
||||
**Gate:** the parity checklist is met and the Theme store adds nothing commercially.
|
||||
|
||||
### Phase 4 — Accounts
|
||||
|
||||
- Customer Account API: hosted login, order history, saved addresses.
|
||||
- Retires the `shop.mozimo.in/account` handoff.
|
||||
|
||||
**Gate:** a customer can sign in and see their orders without leaving mozimo.in.
|
||||
|
||||
### Phase 5 — Retire the theme storefront
|
||||
|
||||
- `shop.mozimo.in` **stays attached to Shopify as the Checkout domain**. Checkout is
|
||||
served from it, so the domain cannot be retired — only the Theme store's pages can.
|
||||
- Redirect the theme's **storefront paths** only, leaving Cart and Checkout paths intact.
|
||||
See [ADR-0003](../../docs/adr/0003-checkout-domain-stays-shop-mozimo-in.md).
|
||||
- Channel audit: paid ads, shopping feeds, marketplace listings, printed QR codes.
|
||||
- SEO: sitemap, canonicals, redirect coverage.
|
||||
|
||||
**Gate:** the theme's pages are retired, Checkout still resolves, and no channel depends
|
||||
on the theme storefront.
|
||||
|
||||
## Out of scope
|
||||
|
||||
Work consciously ruled beyond this destination.
|
||||
|
||||
- **Fully on-domain checkout.** Plus-only. Revisit only via a plan upgrade, which would
|
||||
be a fresh effort, not a resumption of this one.
|
||||
- **Multi-currency and Shopify Markets.** One currency, one country.
|
||||
- **Subscriptions and selling plans.** No product requires one.
|
||||
- **Gift cards.** None exist in the store.
|
||||
- **Inventory and stock display.** Explicitly not tracked and not wanted.
|
||||
- **Customer account migration.** The store already uses new customer accounts, so there
|
||||
is no legacy sunset to outrun.
|
||||
|
||||
## Open decisions
|
||||
|
||||
Held as tickets on `map.md` in this directory, wired with blocking edges.
|
||||
|
||||
- How the dev store is seeded from the live catalogue.
|
||||
- Cart persistence and hydration design under SSR.
|
||||
- What degraded mode looks like to a buyer.
|
||||
- The gifting model: note, wrap, delivery date.
|
||||
- Where the Cart lives in the UI, and what the mini-cart drawer must show.
|
||||
- The parity checklist that gates Phase 5.
|
||||
- What currently points at the Theme store.
|
||||
- Whether the Storefront should absorb the catalogue's content gaps.
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
# Mozimo Commerce
|
||||
|
||||
The commerce domain for mozimo.in: a headless Storefront reading from Shopify, migrating off a classic Shopify theme store.
|
||||
|
||||
## Language
|
||||
|
||||
### Surfaces
|
||||
|
||||
**Storefront**:
|
||||
The headless commerce application in this repository, serving mozimo.in.
|
||||
_Avoid_: Site, website, app
|
||||
|
||||
**Theme store**:
|
||||
The classic Shopify theme storefront at shop.mozimo.in, retained only until the Storefront reaches Parity.
|
||||
_Avoid_: Old site, live shop, Shopify site
|
||||
|
||||
**Checkout**:
|
||||
The Shopify-hosted checkout a buyer is handed off to when they finish with their Cart. It is never rendered by the Storefront.
|
||||
_Avoid_: Our checkout, custom checkout, payment page
|
||||
|
||||
### Catalog
|
||||
|
||||
**Shopify catalog**:
|
||||
The products held in the Shopify store: the commerce truth for what can be bought, at what price, and whether it is available.
|
||||
_Avoid_: Product database, real catalog
|
||||
|
||||
**Editorial catalog**:
|
||||
The hand-curated product data in `src/data/catalog.ts`, used to place products into merchandising slots on the Storefront. Not commerce truth.
|
||||
_Avoid_: Catalog, products file
|
||||
|
||||
**Variant**:
|
||||
A product's purchasable unit, identified by a Shopify variant ID. Every product in the Shopify catalog currently has exactly one Variant, and sizes are modelled as separate products rather than as options.
|
||||
_Avoid_: SKU, option, size
|
||||
|
||||
### Commerce
|
||||
|
||||
**Cart**:
|
||||
The Shopify cart object holding a buyer's intended purchase, identified by a Shopify cart ID. The header's "Bag" label refers to the Cart.
|
||||
_Avoid_: Bag, basket, trolley
|
||||
|
||||
**Parity**:
|
||||
The set of commercial capabilities the Storefront must have before the Theme store can be retired.
|
||||
_Avoid_: Feature complete, launch-ready
|
||||
@@ -0,0 +1,10 @@
|
||||
# Checkout stays Shopify-hosted
|
||||
|
||||
Checkout for the Storefront is Shopify's hosted checkout, reached by following a Cart's `checkoutUrl`. We are not building a custom checkout.
|
||||
|
||||
Shopify sunset the Checkout APIs in 2025-04 — no `checkout*` mutation survives in the Storefront API — so a merchant-built checkout is no longer a supported path. Checkout UI extensions and checkout styling are Plus-only, and this store is on **Grow**. The one documented exception, the Shop Pay Wallet / Shop Components API, is Shop-Pay-only and states that it cannot be used on an existing store.
|
||||
|
||||
## Consequences
|
||||
|
||||
- The buyer's origin changes from `mozimo.in` to `shop.mozimo.in` at payment. That is a brand-owned subdomain, not a `*.myshopify.com` host.
|
||||
- Anything requiring Checkout to be rendered, extended or styled by us is out of reach until the store moves to Plus — at which point it becomes a fresh effort, not a resumption of this one.
|
||||
@@ -0,0 +1,11 @@
|
||||
# One Variant per product; sizes are separate products
|
||||
|
||||
Every product in the Shopify catalog has exactly one Variant, with a single option named `Title`. Size and weight families are modelled as separate products with their own handles rather than as variants — Carnival Celebration S/M/L and Celestial S/L are five products, not two products with options.
|
||||
|
||||
We are keeping that model rather than re-modelling the catalogue. It is verified across all 61 products, and it materially simplifies the Cart work: there is no variant selector to design and no product needs an option-picking UI.
|
||||
|
||||
## Consequences
|
||||
|
||||
- The Cart still needs each product's Variant GID as its merchandise ID, because that is what the Cart API takes. The current `ShopProduct` carries none.
|
||||
- A future engineer will assume a headless cart requires variant selectors. It does not here. Re-modelling sizes into variants later would be a catalogue migration across both storefronts, not a UI change.
|
||||
- Weight is carried in the Variant title (`60gm`, `200gm`) rather than in structured options, so any code needing weight must read it from there.
|
||||
@@ -0,0 +1,14 @@
|
||||
# The Checkout domain stays shop.mozimo.in
|
||||
|
||||
`shop.mozimo.in` remains attached to Shopify as the domain that serves Checkout. The Theme store's **pages** are retired and redirected to mozimo.in; the **domain** is not.
|
||||
|
||||
The Cart API returns a `checkoutUrl` on the store's primary domain, verified empirically to be `https://shop.mozimo.in/cart/c/<token>`. Redirecting the whole domain to mozimo.in would therefore redirect buyers away from their own checkout and break every purchase. The URL tracks store-level domain configuration rather than the host the API is called on, so it cannot be steered by changing where requests are sent.
|
||||
|
||||
## Considered Options
|
||||
|
||||
- **Move Checkout to its own subdomain** (e.g. `checkout.mozimo.in`) so `shop.mozimo.in` can be 301'd wholesale. Rejected: it requires changing the store's primary domain and re-verifying everything downstream of the checkout URL, trading real outage risk for a cosmetic win — the buyer sees a brand-owned subdomain either way.
|
||||
|
||||
## Consequences
|
||||
|
||||
- "Retire the theme store" means retiring its storefront pages, never the domain. Any ticket phrased as "301 `shop.mozimo.in`" is wrong.
|
||||
- The redirect must exclude Cart and Checkout paths.
|
||||
@@ -0,0 +1,785 @@
|
||||
# Shopify admin operations: product CSV round-trip fidelity + customer accounts system (verified 2026-10-13)
|
||||
|
||||
**Purpose.** Two focused factual questions, answered only from Shopify-owned primary sources:
|
||||
|
||||
1. What survives a **manual admin product CSV export → import round trip** between two Shopify stores,
|
||||
in particular product metafields.
|
||||
2. Which **customer accounts system** the merchant's `Settings > Customer accounts` page describes, and
|
||||
the lifecycle/plan facts around new vs legacy customer accounts.
|
||||
|
||||
**Facts only. No recommendations.**
|
||||
|
||||
**Store context** (from the parent task and the sibling note
|
||||
[shopify-cart-probe-2026-10.md](shopify-cart-probe-2026-10.md)): `mozimo.in`, shop GID
|
||||
`gid://shopify/Shop/74467967222`, 61 products each with exactly one variant, 16 collections, 20 products
|
||||
with nutrition/ingredient metafields.
|
||||
|
||||
---
|
||||
|
||||
## 0. How this was verified (and one method finding worth reusing)
|
||||
|
||||
* **`shopify.dev`:** every page was fetched as raw Markdown by appending `.md` to the HTML URL
|
||||
(e.g. <https://help.shopify.com/en/manual/products/import-export/export-products.md>), which the site
|
||||
itself documents as available for any page.
|
||||
* **`help.shopify.com` — the 403 was bypassed by the same `.md` suffix.** Bare help-centre URLs return
|
||||
HTTP **403** with `<title>Verifying your connection...</title>` (Shopify's bot challenge) for `curl`,
|
||||
for the `web_fetch` tool, and regardless of `User-Agent` (tested: default `curl`, Chrome desktop,
|
||||
Safari desktop, Googlebot). Appending `.md` to the **same** URL returns **HTTP 200 with the complete,
|
||||
untruncated raw Markdown article**. Confirmed pairs, same second:
|
||||
|
||||
| URL | HTTP |
|
||||
| - | - |
|
||||
| `https://help.shopify.com/en/manual/products/import-export/export-products` | **403** |
|
||||
| `https://help.shopify.com/en/manual/products/import-export/export-products.md` | **200** (8,029 bytes) |
|
||||
|
||||
A locale-prefixed path is required: `/en/manual/...`. Bare `/manual/...` (no `/en`) also **403**s.
|
||||
This was used for essentially every `help.shopify.com` claim below, so the impact of the bot challenge
|
||||
on this research was **zero**.
|
||||
* **The official sample product CSV was downloaded from the help centre**:
|
||||
<https://help.shopify.com/csv/product_template.csv> → HTTP 200, 6,536 bytes, 61 header columns.
|
||||
Its header row and example rows are quoted verbatim in [§1.3](#13-what-is-actually-in-the-exported-file-headers)
|
||||
and [§1.5](#15-variants-and-the-title--default-title-question). Note: the canonical URL without the
|
||||
`/en` prefix works for this asset (`/csv/product_template.csv`), while `/en/csv/product_template.csv`
|
||||
returns the HTML app shell, not the CSV.
|
||||
* **Shopify changelog** (`https://shopify.dev/changelog/...` and `https://changelog.shopify.com/posts/...`)
|
||||
— the latter supports no `.md` (HTTP 406) and was extracted from its HTML.
|
||||
* **Shopify community forums** were read via the forum's own JSON API
|
||||
(`https://community.shopify.com/t/<id>.json`, `https://community.shopify.dev/t/<id>.json`,
|
||||
`.../search.json?q=`). These are **not** documentation; every community claim below is labelled as such
|
||||
and is never used as the sole support for a fact that a primary source could carry.
|
||||
* **What is marked UNVERIFIED** is listed inline and collected in [§3](#3-verification-notes-and-gaps).
|
||||
|
||||
---
|
||||
|
||||
# Question 1 — Product CSV export/import round-trip fidelity
|
||||
|
||||
## 1.1 The exact export click path, and the export options
|
||||
|
||||
Source: <https://help.shopify.com/en/manual/products/import-export/export-products>
|
||||
(<https://help.shopify.com/en/manual/products/import-export/export-products.md>).
|
||||
|
||||
Admin path and options, verbatim from the doc:
|
||||
|
||||
1. Go to **Products** (<https://admin.shopify.com/products>).
|
||||
2. Click **Export**.
|
||||
3. In the dialog, choose the products:
|
||||
* the current page of products
|
||||
* **All products**
|
||||
* products you have **selected**
|
||||
* products that match your **search and filters**
|
||||
4. Choose the file type:
|
||||
* **CSV file for Excel, Numbers, or another spreadsheet program** — "if you plan to use a spreadsheet
|
||||
program to edit your product CSV file"
|
||||
* **Plain CSV file** — "if you plan to use a plain-text editor"
|
||||
5. Click **Export products**.
|
||||
|
||||
**How the file reaches you (this is conditional, and matters for a 61-product store):**
|
||||
|
||||
| Situation | Delivery |
|
||||
| - | - |
|
||||
| **All products** selected | CSV is **emailed** to you; if you aren't the store owner it is also emailed to the store owner |
|
||||
| **Current page** or **Selected products**, and **any single product has >100 variants** | CSV is **emailed** |
|
||||
| **Current page** or **Selected products**, and **all products have <100 variants** | CSV is **downloaded by your browser** |
|
||||
|
||||
Same source (`.md`, "Export your products" preamble).
|
||||
|
||||
**Only one file is produced** — a single products CSV. There is no second artefact and no image bundle.
|
||||
No doc states the filename; **UNVERIFIED** (the docs never name the downloaded/emailed file).
|
||||
|
||||
**Encoding:** "A CSV file with UTF-8 encoding is the only file format that can be used to import to or
|
||||
export from Shopify." (same source, "Use a text editor to edit your products CSV file").
|
||||
|
||||
## 1.2 Filtered export (relevant if the export must be split)
|
||||
|
||||
1. On **Products**, add a filter so only the wanted products are listed.
|
||||
2. Export, selecting the option that exports **only the products matching your filter**.
|
||||
|
||||
Source: <https://help.shopify.com/en/manual/products/import-export/export-products.md>.
|
||||
A documented export failure mode for large files: *"There was an error exporting your products. Try again
|
||||
in a few minutes"* indicates the export timed out (thousands of variants); the documented workaround is to
|
||||
filter and export smaller subsets (same source, "Troubleshooting product exports").
|
||||
|
||||
## 1.3 What is actually in the exported file (headers)
|
||||
|
||||
The docs publish the full column list at
|
||||
<https://help.shopify.com/en/manual/products/import-export/using-csv#csv-file-description>.
|
||||
|
||||
The **official sample CSV** (<https://help.shopify.com/csv/product_template.csv>, fetched 2026-10-13)
|
||||
carries this exact header row, 61 columns, quoted verbatim:
|
||||
|
||||
```
|
||||
Title,URL handle,Description,Vendor,Product category,Type,Tags,Published on online store,Status,SKU,Variant Barcodes,Option1 name,Option1 value,Option1 Linked To,Option2 name,Option2 value,Option2 Linked To,Option3 name,Option3 value,Option3 Linked To,Price,Compare-at price,Cost per item,Charge tax,Tax code,Unit price total measure,Unit price total measure unit,Unit price base measure,Unit price base measure unit,Inventory tracker,Inventory quantity,Continue selling when out of stock,Weight value (grams),Weight unit for display,Requires shipping,Fulfillment service,Product image URL,Image position,Image alt text,Variant image URL,Gift card,SEO title,SEO description,Color (product.metafields.shopify.color-pattern),Google Shopping / Google product category,Google Shopping / Gender,Google Shopping / Age group,Google Shopping / Manufacturer part number (MPN),Google Shopping / Ad group name,Google Shopping / Ads labels,Google Shopping / Condition,Google Shopping / Custom product,Google Shopping / Custom label 0,Google Shopping / Custom label 1,Google Shopping / Custom label 2,Google Shopping / Custom label 3,Google Shopping / Custom label 4,Packed product length,Packed product width,Packed product height,Packed product dimension unit
|
||||
```
|
||||
|
||||
Two further header sources exist and do **not** agree word-for-word with either the sample or the current
|
||||
documentation — see [§1.5](#15-variants-and-the-title--default-title-question) and
|
||||
[§1.9](#19-other-things-that-silently-fail-or-are-commonly-lost):
|
||||
|
||||
* A **legacy** header list is still published in the help centre's import-troubleshooting article:
|
||||
`Handle,Title,Body (HTML),Vendor,Type,Tags,Published,Option1 Name,Option1 Value,...,Image Src,Image Alt Text`
|
||||
(<https://help.shopify.com/en/manual/products/import-export/common-import-issues.md>, "Invalid CSV
|
||||
header: missing headers"). The authoritative current list on the same help centre is the `using-csv`
|
||||
one cited above; the article itself notes Shopify *"maintains backward compatibility with older column
|
||||
names"* (<https://help.shopify.com/en/manual/products/import-export/using-csv.md>).
|
||||
* **Market columns** are appended conditionally: *"If you have **Markets** set up on your store, then your
|
||||
CSV export includes columns for some of your markets... `Included / [Market]`, `Price / [Market]`,
|
||||
`Compare At Price / [Market]`."* A market gets columns only if exactly one of its active catalogs isn't
|
||||
shared with another market. The primary market always appears as `Included / [Primary]` and the default
|
||||
international market as `Included / International` (same source, "International CSV columns"). So a
|
||||
Mozimo export is **not** exactly 61 columns; it carries extra market columns.
|
||||
|
||||
## 1.4 The exact import click path, and how the import behaves
|
||||
|
||||
Source: <https://help.shopify.com/en/manual/products/import-export/import-products>
|
||||
(<https://help.shopify.com/en/manual/products/import-export/import-products.md>).
|
||||
|
||||
1. Go to **Products**.
|
||||
2. Click **Import**.
|
||||
3. Click **Add file**, select the CSV.
|
||||
4. *Optional:* deselect **Publish new products to all sales channels** to publish new products only to the
|
||||
online store.
|
||||
5. *Optional:* select **Overwrite products with matching handles**.
|
||||
6. Click **Upload and continue**.
|
||||
7. **Review the details about the import**, then click **Import products**.
|
||||
|
||||
**Is it a background job?** The doc describes an upload → review → confirm flow, followed by a
|
||||
**confirmation email from Shopify**, and an **activity log** you can check afterwards. It never calls it a
|
||||
"background job". It does say explicitly: *"Product imports started with a CSV file can't be canceled once
|
||||
they begin, and you can't view a history of past imports. To review recent changes made by an import,
|
||||
check your store's activity log."* A separate note states that after upload *"you receive a confirmation
|
||||
email from Shopify to the email address of your user account"* (same source, "Import products from
|
||||
another store"). **UNVERIFIED:** the doc does not state whether processing continues after the admin tab
|
||||
is closed, nor any progress/status surface beyond the email and activity log.
|
||||
|
||||
**Dry run / preview?** There is a step 7 "Review the details about the import". The docs do **not**
|
||||
describe it as a data preview, a row-level validation report, or a dry run — the phrase is "details about
|
||||
the import". **No documented dry-run/preview exists.** **UNVERIFIED** what that review screen lists.
|
||||
|
||||
**How errors are reported.** Documented error surface is (a) a set of named error messages, and (b)
|
||||
per-line messages. From <https://help.shopify.com/en/manual/products/import-export/common-import-issues.md>:
|
||||
|
||||
* `Daily variant creation limit reached, try again` — for stores with ≥500,000 variants (excluding Plus),
|
||||
max **10,000 new variants per 24 hours** created by CSV uploads or API; the 24-hour timer starts at the
|
||||
first upload of the day.
|
||||
* `Fulfillment service can't be blank` — must be a fulfilment-service name, or `manual`.
|
||||
* `Ignored line #-## because handle 'example' already exists` — "The handle is used to uniquely identify
|
||||
each individual product."
|
||||
* `Ignored line #-## because it did not contain product data` — a handle with no product title.
|
||||
* `Illegal quoting on line`, `Missing or stray quote on line`, `Unexpected characters or symbols`
|
||||
(non-UTF-8 encoding), `Network error: Unexpected token < in JSON at position 0` (column headers don't
|
||||
match the template exactly; **headers are case-sensitive**, e.g. `Handle` not `handle`),
|
||||
`Not a valid product category`, `Inventory quantity can't be blank`, `Inventory quantity is not a
|
||||
number`, `Inventory policy is not included in the list`, `Validation failed: price can't be blank`,
|
||||
`Validation failed: options are not unique`, `Import fails with error "Line is invalid (No details)"`.
|
||||
* Image-specific validation failures: `Validation failed: An error occurred while trying to download the
|
||||
image` ("An image file is missing") and `Validation failed: getaddrinfo: nodename nor servname provided,
|
||||
or not known` (image URL needs `http://`/`https://` and a publicly accessible direct link) — same source.
|
||||
|
||||
**Is it idempotent? Does re-importing update by handle or duplicate?** This is documented precisely
|
||||
(<https://help.shopify.com/en/manual/products/import-export/using-csv#overwriting-csv-file> and the
|
||||
`import-products` page):
|
||||
|
||||
* **With `Overwrite products with matching handles` selected:** *"when the handle in the import CSV file
|
||||
matches an existing handle in your products list, the values in the CSV file overwrite the values in the
|
||||
matching columns in the existing product list."*
|
||||
→ re-running the same file **updates** existing products; it does not create duplicates.
|
||||
* **Without that option selected:** *"the products that match an existing handle are ignored during CSV
|
||||
import."*
|
||||
→ re-running the same file is a no-op for those products, not a duplicate generator.
|
||||
* The `Ignored line #-## because handle ... already exists` error message corroborates handle-as-key
|
||||
(<https://help.shopify.com/en/manual/products/import-export/common-import-issues.md>).
|
||||
* A conflicting handle **inside one file** also fails: *"Ignored line #-## because handle `example`
|
||||
already exists"*.
|
||||
* **CSV cannot bulk-delete products** — use bulk actions in **Products**
|
||||
(<https://help.shopify.com/en/manual/products/import-export/using-csv#overwriting-csv-file>).
|
||||
|
||||
**File limit:** *"Your product CSV can't exceed 15 MB."* If the upload errors or times out, split the file
|
||||
(<https://help.shopify.com/en/manual/products/import-export/import-products.md>).
|
||||
|
||||
**Required columns.** Importing new products: only `Title` (plus `URL handle` if adding variants).
|
||||
Updating products: `URL handle` and `Title`. Header names are case-sensitive.
|
||||
(<https://help.shopify.com/en/manual/products/import-export/using-csv.md>, "Required columns in the
|
||||
product CSV file".)
|
||||
|
||||
**Not a migration tool for anything else.** Nothing in the product CSV docs covers customers, orders,
|
||||
discounts, gift cards, or files.
|
||||
|
||||
## 1.5 Variants, and the `Title` / `Default Title` question
|
||||
|
||||
**Representation.** One row per variant, with the product-level data repeated on the first row and only
|
||||
the handle repeated on subsequent rows. The docs describe two shapes
|
||||
(<https://help.shopify.com/en/manual/products/import-export/using-csv.md>, "Create your product CSV file"):
|
||||
|
||||
* *"Simple product: A simple product doesn't include variants. If you upload a product that doesn't have
|
||||
variants, then enter all the fields for the product in the first row along with the URL for the first
|
||||
image. In the following rows, only enter the handle and the URL for each additional image."*
|
||||
* *"Product with variants: ... enter all the fields for the product in the first row along with the URL for
|
||||
the first image. In the following rows, enter the URL handle. Then skip the Title, Description, Vendor,
|
||||
and Tags columns. Fill out the rest of the variants' details and each image URL."*
|
||||
|
||||
The option columns are `Option1 name` / `Option1 value` / `Option1 Linked To`, up to `Option3`
|
||||
(case in the current docs: `Option1 name`; the sample CSV also uses `Option1 name`). Up to 3 options per
|
||||
product (same source, column table).
|
||||
|
||||
**On the merchant's exact shape (61 products, one variant each, option name `Title`):** the published
|
||||
guidance is internally inconsistent, and this is the single most quotable wrinkle found:
|
||||
|
||||
| Source | What it says for a one-option product |
|
||||
| - | - |
|
||||
| Current column reference, `Option1 name` row: *"If a product has only one option, then this value should be `Default Title`."* | **`Default Title`** — <https://help.shopify.com/en/manual/products/import-export/using-csv.md> |
|
||||
| **Official sample CSV** (`/csv/product_template.csv`), row 16, product `Example Perfume` | `Option1 name = Title`, `Option1 value = Premium` — i.e. option name `Title` with an **arbitrary** value |
|
||||
| Also in the sample, `Title` is used as the option *name* for the single-option product, while multi-option products use `Size`/`Color`/`Format` (verbatim rows quoted in this file's own `python3 csv` alignment, from the downloaded sample) | **`Title`** |
|
||||
| Community help article (Shopify-staff-authored account, but a forum post, 2023-05-09): *"Change the Option1 Name column value to Title and the Option1 Value column value to Default Title."* | **`Title` + `Default Title`** — <https://community.shopify.com/t/214856.json> |
|
||||
|
||||
So the docs' `Default Title` instruction is about the **value**; the sample uses `Title` as the **name**.
|
||||
The merchant's stored shape (option name literally `Title`) is exactly the shape the official sample CSV
|
||||
represents. Consequence for the round trip:
|
||||
|
||||
* Re-importing an **unmodified export** reproduces the exported shape by construction — the export writes
|
||||
whatever the store holds, and import consumes the same columns. **UNVERIFIED:** no primary source
|
||||
states, for this admin, the literal string an *export* writes into `Option1 value` for a one-variant
|
||||
product whose option name is `Title`. The only way to establish it is to read the merchant's own export.
|
||||
* Real risk documented around the option columns, not around the name itself:
|
||||
* *"Changing data in the `Option1 value`, `Option2 value`, or `Option3 value` columns of the product CSV
|
||||
file deletes existing variant IDs, and creates new variant IDs."*
|
||||
* *"If you don't include the `Option1 name` and `Option1 value` columns, then a new default variant is
|
||||
created and existing variants are deleted."*
|
||||
* *"If a non-required column is included in the import CSV file that relies on other column data not
|
||||
included in the file, then existing data is deleted or removed. For example, if the `SKU` column is
|
||||
included in the import CSV file, but not the `Option1 value` and `Option1 name` columns, then the
|
||||
product variant option is deleted."*
|
||||
* Option-name conflicts fail the whole import with `Line is invalid (No details)`; the documented
|
||||
workaround is a temporary option name in a first import, then a second import to rename.
|
||||
* *"Validation failed: options are not unique."*
|
||||
All from <https://help.shopify.com/en/manual/products/import-export/using-csv.md> and
|
||||
<https://help.shopify.com/en/manual/products/import-export/import-products.md>.
|
||||
|
||||
## 1.6 METAFIELDS — do `custom.ingredients` / `custom.energy_kcal` survive? **Yes, with conditions.**
|
||||
|
||||
**Short answer.** Product metafields **do** travel in the product CSV and are a first-class part of the
|
||||
documented CSV column set — but only metafields that have a **definition**, and the round trip is a
|
||||
two-part operation: (1) definitions in the destination store, (2) the values, which ride in the CSV. There
|
||||
is **no separate metafield CSV file** in the admin. Variant metafields are **excluded** from product CSV
|
||||
entirely.
|
||||
|
||||
**Primary source:** <https://help.shopify.com/en/manual/products/import-export/using-csv.md>
|
||||
(§"Metafields in CSV columns", §"Variant metafields", and the `Metafields` row of the column table) —
|
||||
the same page saved as raw Markdown at
|
||||
<https://help.shopify.com/en/manual/products/import-export/using-csv.md>.
|
||||
|
||||
Verbatim facts:
|
||||
|
||||
1. *"Product metafields are supported in product bulk import/export using CSV files. **After a product
|
||||
metafield is defined, it's included in your product CSV exports.**"*
|
||||
2. Header format: `<name>` (`product.metafields.<namespace>.<key>`) — e.g.
|
||||
`Fabric (product.metafields.shopify.fabric)` — **or** bare `product.metafields.<namespace>.<key>`.
|
||||
The sample CSV carries a live example column: `Color (product.metafields.shopify.color-pattern)`.
|
||||
The column-table row repeats the format as `NAME; (product.metafields.custom.KEY)`.
|
||||
The namespace/key are found in **Settings > Custom data > Products** (same page).
|
||||
→ For the merchant: `custom.ingredients` and `custom.energy_kcal` appear as
|
||||
`Ingredients (product.metafields.custom.ingredients)` and `Energy kcal (product.metafields.custom.energy_kcal)`
|
||||
(the display name is the definition's name; the parenthesised identifier is authoritative).
|
||||
3. **Supported metafield types in CSV bulk import/export** (verbatim list): `boolean`, `color`
|
||||
(`#ff00cc`), `date`, `date_time`, `dimension` (`25.0cm`), `list.color`, `list.date`, `list.date_time`,
|
||||
`list.dimension`, `list.metaobject_reference`, `list.number_decimal`, `list.number_integer`,
|
||||
`list.product_reference`, `list.url`, `list.volume`, `list.weight`, `money` (`5.99 CAD`),
|
||||
`multi_line_text_field`, `number_decimal`, `number_integer`, `product_reference`, `shopify.disclosure`,
|
||||
`single_line_text_field`, `url`, `volume`, `weight`.
|
||||
* `custom.ingredients` as text → `multi_line_text_field` (**supported**; example given is literally
|
||||
`"Ingredients\nFlour\nWater\nMilk\nEggs"`).
|
||||
* `custom.energy_kcal` as a number → `number_integer` or `number_decimal` (**supported**).
|
||||
4. **Variant metafields do NOT round-trip.** Verbatim: *"Variant metafields aren't supported for product
|
||||
CSV import/export. To edit your variant metafields in bulk, you need to use the variant bulk editor,
|
||||
and then add metafield columns."* → <https://help.shopify.com/en/manual/products/import-export/using-csv.md>;
|
||||
bulk editor how-to at <https://help.shopify.com/en/manual/custom-data/metafields/bulk-edit-metafields.md>.
|
||||
5. **Definition-before-value requirement.** *"Metafield definitions act as templates that specify what part
|
||||
of your store a metafield applies to and what values the metafield can have. **Before you can add
|
||||
metafield values to specific products... you need to add the metafield definition for that
|
||||
information.**"* — <https://help.shopify.com/en/manual/custom-data/metafields/metafield-definitions.md>.
|
||||
Definitions live at <https://admin.shopify.com/metafields>; up to 256 definitions per type.
|
||||
The CSV page's phrasing *"after a product metafield is defined, it's included in your product CSV
|
||||
exports"* means an **undefined** product metafield is not guaranteed to appear as a column, and the
|
||||
docs never state what happens to an undefined metafield's *value* during import.
|
||||
→ Treat "metafield values with no matching definition in the destination store" as **UNVERIFIED /
|
||||
not documented**. (A community thread on 2026-09-24 reports category-linked metafields being added as
|
||||
columns for products with a category — <https://community.shopify.com/t/686939.json> — but it is a
|
||||
forum post, not documentation.)
|
||||
6. **No separate metafield export/import file or step exists in the admin.** The metafields help section
|
||||
(<https://help.shopify.com/en/manual/custom-data/metafields.md>) lists no export/import feature; its
|
||||
bulk tooling is the admin bulk editor ("Editing metafields in bulk": *"You can use the bulk editor tool
|
||||
to make changes to multiple metafields at the same time. You can edit metafields in bulk for products,
|
||||
variants, collections, and customers."*), which edits in place and does not produce a file.
|
||||
→ The product CSV **is** the supported admin mechanism for moving product metafield values.
|
||||
7. **Overwrite semantics apply to metafield columns too, and they are destructive.** With
|
||||
**Overwrite products with matching handles** on: *"If a non-required column in the import CSV file is
|
||||
blank, then the matching value in the product list is overwritten as blank."* So a blank metafield cell
|
||||
in an overwrite import **clears** the destination value
|
||||
(<https://help.shopify.com/en/manual/products/import-export/using-csv.md#overwriting-csv-file>).
|
||||
8. **Metaobjects are not exported as content.** Metafield *references* to metaobjects are supported as
|
||||
identifiers (`list.metaobject_reference` accepts GIDs or handles), but no doc says metaobject
|
||||
definitions/entries travel in the product CSV. **UNVERIFIED** and not mentioned anywhere in the CSV or
|
||||
metafields help pages.
|
||||
|
||||
**Metafield-only alternative for moving values between stores:** the docs describe no file-based
|
||||
alternative in the admin. The developer-surface option is the GraphQL Admin API
|
||||
`metafieldsSet` (<https://shopify.dev/docs/api/admin-graphql/latest/mutations/metafieldsSet>) with
|
||||
`MetafieldDefinition` creation (<https://shopify.dev/docs/api/admin-graphql/latest/input-objects/MetafieldDefinitionInput>),
|
||||
and Shopify's metafields developer guide (<https://shopify.dev/docs/apps/build/metafields>,
|
||||
<https://shopify.dev/docs/apps/build/custom-data/metafields>). This note records only that these exist;
|
||||
no recommendation is made.
|
||||
|
||||
## 1.7 Images
|
||||
|
||||
**Primary source:** <https://help.shopify.com/en/manual/products/import-export/using-csv.md>
|
||||
("Prepare your images", "Adding multiple product images in a CSV file", and the `Product image URL`,
|
||||
`Image position`, `Image alt text`, `Variant image URL` rows) and
|
||||
<https://help.shopify.com/en/manual/products/import-export/export-products.md> ("Access your product
|
||||
image files").
|
||||
|
||||
* **The CSV carries image URLs, not image bytes.** Columns: `Product image URL`, `Image position`,
|
||||
`Image alt text`, `Variant image URL`. *"A CSV file can only contain text, therefore, you need to make
|
||||
sure that all product images are on an existing website. Those image URLs are only used during the CSV
|
||||
file importing process. You can delete the URLs after your import is complete."*
|
||||
* **Import re-hosts the image.** *"Enter the URL for the product image. **Shopify downloads the images
|
||||
during the import and re-uploads them in your store.**"* → a `cdn.shopify.com` URL from the source store
|
||||
is fetched and copied into the destination store.
|
||||
* **The source store must stay alive and its images publicly reachable until the import completes** —
|
||||
this is the documented failure mode for exactly the migration case in question:
|
||||
* *"When you export your products, the images associated with each product aren't included in the CSV
|
||||
file. If you import the products to a new store, then the product images are generated only if they
|
||||
remain viewable on a publicly accessible website."*
|
||||
* *"To ensure that your product images remain accessible to you, leave your original store and its
|
||||
products intact until you finish the import process."*
|
||||
* *"If you remove products or product images from your Shopify store after exporting, and then try to
|
||||
import those products without updating their image URLs, then your import will fail."*
|
||||
* *"Your product images must be uploaded to a publicly accessible URL. That is, they should be behind an
|
||||
`https://` protocol with no password protection."*
|
||||
* Documented image errors: `Validation failed: An error occurred while trying to download the image`
|
||||
("An image file is missing"), `Validation failed: getaddrinfo: nodename nor servname provided, or not
|
||||
known` (malformed URL / not a direct public link), and `Incorrect header check` — *"Remove the image
|
||||
from that line and try again. If that resolves the issue, then try hosting the image somewhere else."*
|
||||
(<https://help.shopify.com/en/manual/products/import-export/common-import-issues.md>).
|
||||
* **Alt text survives** — `Image alt text` is a first-class column. Max 512 characters, optimal ≤125.
|
||||
* **Image order survives** via `Image position` (ascending, starting at 1). One row per image.
|
||||
* **Filename constraints on import:** the image file name *"is final: you can't edit it after the image has
|
||||
been uploaded to your store"* and must not contain `_thumb`, `_small`, or `_medium` suffixes.
|
||||
* **Cap and media types:** up to **250 images per product**; *"You can use only image URLs in CSV files.
|
||||
3D models and videos aren't supported for the CSV."*
|
||||
* **Files-area duplication:** importing an image that lives in the destination's **Content > Files** area
|
||||
*"is redownloaded and duplicated"* (the copy lands in `/products/`, the original stays in `/files/`;
|
||||
documented as expected behaviour).
|
||||
* **Rate limits on the image fetch:** **UNVERIFIED.** No primary source found stating a per-import image
|
||||
fetch rate limit or concurrency cap.
|
||||
* **Community-reported (not documentation) silent image loss:** a 2026-08-07 forum post from a
|
||||
vendor-affiliated CSV-tool account describes images vanishing after an import that reported "Success",
|
||||
attributing it to the background image-ingestion step
|
||||
(<https://community.shopify.com/t/396027.json>). Treat as unverified anecdote; note that its author
|
||||
advertises a competing tool.
|
||||
|
||||
## 1.8 Collections — **NOT in the export. This is a real gap.**
|
||||
|
||||
**Primary source:** <https://help.shopify.com/en/manual/products/import-export/using-csv.md>
|
||||
("Collection column exception").
|
||||
|
||||
* *"When you export a CSV file, the `Collection` column isn't included."* — collections and membership are
|
||||
**absent** from the product CSV export.
|
||||
* The **only** supported CSV route is a hand-added column on **import**:
|
||||
* *"The `Collection` column is the only column that you can add to the CSV file that doesn't break the
|
||||
format. You can't add any other columns to your CSV file."*
|
||||
* Header must be exactly `Collection`; it can go anywhere in the file.
|
||||
* *"Enter the name of the collection that you want to add this product to."*
|
||||
* *"If it's an existing collection that has conditions, then the product needs to meet the conditions
|
||||
for the collection."*
|
||||
* *"If the collection doesn't already exist, then a collection is created."*
|
||||
* **Hard limit: *"You can add a product to only one collection."*** Value may be blank; max length 255
|
||||
characters.
|
||||
* Consequence for a 16-collection catalogue with products in more than one collection: the CSV can only
|
||||
express one membership per product. (Multiple rows for the same handle collide with the
|
||||
handle-uniqueness rule that produces `Ignored line #-## because handle ... already exists` —
|
||||
<https://help.shopify.com/en/manual/products/import-export/common-import-issues.md>.)
|
||||
* **Supporting context on the collections model:** a new collections model is replacing legacy manual and
|
||||
smart collections; where it is available, previous collections were auto-converted. Collections can be
|
||||
populated by manual inclusion, inclusion conditions, manual exclusion, and exclusion conditions, and can
|
||||
nest. Collections are managed at <https://admin.shopify.com/collections>. Smart/conditional collections
|
||||
self-populate from conditions, so membership is reproducible by conditions rather than by a file.
|
||||
Source: <https://help.shopify.com/en/manual/products/collections.md> and
|
||||
<https://help.shopify.com/en/manual/products/collections/create-collection.md>.
|
||||
* **The only documented bulk membership mechanism for a product in many collections is the admin bulk
|
||||
editor** (<https://help.shopify.com/en/manual/shopify-admin/productivity-tools/bulk-editing.md>,
|
||||
reachable from **Products** → select products → **Edit products**), or the GraphQL Admin API
|
||||
(`collectionAddProducts` / `CollectionInput`; collection reference:
|
||||
<https://shopify.dev/docs/api/admin-graphql/latest/objects/Collection>).
|
||||
There is **no** documented collection export/import file (no collections CSV in the help centre, and
|
||||
**UNVERIFIED** whether one exists anywhere in the admin).
|
||||
|
||||
## 1.9 Other things that silently fail or are commonly lost
|
||||
|
||||
All from <https://help.shopify.com/en/manual/products/import-export/using-csv.md> unless noted.
|
||||
|
||||
| Field | Round-trip behaviour |
|
||||
| - | - |
|
||||
| **Status (active/draft/archived)** | `Status` column. *"If this column is present, then it needs a value. If the column isn't present, then the product status is automatically uploaded as `active`."* Blank `Status` with a present column → import error surface (`Status` requires a value). |
|
||||
| **Published on online store** | `Published on online store` column, `true` (default) / `false`, governs only the **online store** sales channel. |
|
||||
| **Sales-channel publication beyond online store** | *"CSV files can't be used to bulk update product availability on other sales channels."* The import dialog's **Publish new products to all sales channels** checkbox (default on) is the only channel control, and it applies to **new** products only — deselect it to publish new products to the online store only (<https://help.shopify.com/en/manual/products/import-export/import-products.md>). Per-channel availability must be set in the admin or by API. |
|
||||
| **SEO title / SEO description** | Both columns exist and round-trip, with a documented caveat: *"The SEO Title and SEO Description fields in exported CSV files are only populated when you've manually customized them in your products. If these fields haven't been edited, then they'll be blank in the export. This is expected behavior, as the CSV can only export saved data. When these fields are blank, SEO information is automatically generated based on your product title and description."* (<https://help.shopify.com/en/manual/products/import-export/export-products.md>). Limits: SEO title ≤70 chars, SEO description ≤320 chars (using-csv). On import, blank SEO title → product `Title` used; blank SEO description → product `Description` used. |
|
||||
| **Weight** | `Weight value (grams)` (numeric grams only, no unit, no decimals — e.g. 5125 for 5.125 kg) and `Weight unit for display` (`g`/`kg`/`lb`/`oz`; default `kg`). Default value 0 if blank. |
|
||||
| **Inventory quantity** | `Inventory quantity` — *"This applies only to Shopify stores that have a single location."* Default 0 if blank. For multi-location stores the docs route you to the **inventory CSV** (<https://help.shopify.com/en/manual/inventory-and-locations/setup/inventory-csv>) or the Inventory API. |
|
||||
| **Inventory tracker** | `Inventory tracker` = `shopify`, `shipwire`, `amazon_marketplace_web`, or blank if untracked. *"If existing inventory tracking options are removed, then inventory is no longer tracked."* On export, third-party-fulfilment variants show `manual` (or `gift_card` for gift-card variants); `Inventory tracker` shows `shopify` when tracking is on and is blank when it isn't (<https://help.shopify.com/en/manual/products/import-export/export-products.md>). |
|
||||
| **Tolerances** | `Continue selling when out of stock` = `deny` (default) / `continue`. `Charge tax` and `Requires shipping` are boolean columns. `Cost per item` exists. |
|
||||
| **Tags** | `Tags` column; max 250 tags/product; comma-separated in one cell, quoted; *"Tags aren't case sensitive."* |
|
||||
| **Product type** | `Type` column — free-form label, round-trips. |
|
||||
| **Product category** | `Product category` — must match Shopify's Standard Product Taxonomy breadcrumb or ID exactly, else `Not a valid product category`. |
|
||||
| **Handle** | `URL handle` is the join key for overwrite behaviour; must be unique across the file. Changing it changes the product URL. |
|
||||
| **SKU / barcodes** | `SKU` (required non-blank with a custom fulfilment service) and `Variant Barcodes` (up to 20, semicolon-separated, optional `type:` prefix). A file containing **both** `Barcode` and `Barcodes` columns fails to import. |
|
||||
| **Gift cards** | `Gift card` column exists and can bulk-edit details, but *"you can't create a gift card by importing a product CSV file."* |
|
||||
| **Media** | Videos and 3D models are **not** in the CSV (images only). |
|
||||
| **Dimension columns** | `Packed product length/width/height` + `Packed product dimension unit` exist. |
|
||||
| **Sorting the CSV in a spreadsheet** | *Caution:* *"Sorting your CSV file in a spreadsheet program can cause product variants or image URLs to become disassociated. Importing a CSV file that has been sorted might overwrite your existing products with bad data, which can't be recovered."* (export doc) / *"Importing a CSV file that has been sorted by a spreadsheet editor such as Microsoft Excel or Apple Numbers might cause your products to be removed from their relevant image links on the CSV, and your product's images will be lost."* (import doc). |
|
||||
| **Excel delimiter trap** | *"If you use Excel to edit your CSV, then check Excel's export settings when you export the CSV to ensure that your file uses commas between values."* |
|
||||
| **Backward-compatibility of column names** | *"If you use an older CSV template, then you might have different column names than the ones that are listed on this page. Shopify maintains backward compatibility with older column names, but you should consider updating to the current format."* |
|
||||
| **Overwrite side effects** | Blank non-required cell overwrites the destination value **as blank**; an omitted column leaves the destination value unchanged; including a column whose dependencies are absent deletes/removes data. |
|
||||
| **Import limits** | 15 MB file cap; 10,000 new variants/24h for stores with ≥500,000 variants (excluding Plus); can't be cancelled; no import history; review via activity log. |
|
||||
| **Single-location assumption** | Both the sample-file note and the `Inventory quantity` row tie the CSV's quantity field to single-location stores. **UNVERIFIED:** no doc says what happens to `Inventory quantity` on import into a multi-location store (the docs only redirect to the inventory CSV / Inventory API). |
|
||||
|
||||
---
|
||||
|
||||
# Question 2 — New vs legacy customer accounts, from admin UI evidence
|
||||
|
||||
## 2.1 Which system does that UI describe? **New customer accounts.** (Two independently primary legs.)
|
||||
|
||||
The evidence quoted for the merchant's admin page is from the parent task's description: a **Sign-in
|
||||
links** toggle, a **Customer accounts** section with **Configurations / Customize** and **Authentication /
|
||||
Manage**, **Self-serve returns** (off), **Store credit** (on), and a **URL** row with **Change domain**
|
||||
showing `https://shopify.com/74467967222/account`.
|
||||
|
||||
**Leg A — feature availability.** The help centre's own comparison table marks **Self-serve returns** and
|
||||
**Store credit** as available in **Customer accounts** and **not** available in **legacy customer
|
||||
accounts** (<https://help.shopify.com/en/manual/customers/customer-accounts/upgrade/compare-features.md>):
|
||||
|
||||
| Feature | Customer accounts (new) | Legacy customer accounts |
|
||||
| - | - | - |
|
||||
| Self-serve returns | ✔ | ✘ |
|
||||
| Store credit | ✔ | ✘ |
|
||||
| Saved payment methods | ✔ | ✘ |
|
||||
| Branding source | checkout settings | **online store theme settings** |
|
||||
| Customisation model | app blocks in a visual editor | **"Requires editing Liquid code"** |
|
||||
| Sign-in | one-time 6-digit email code; optional Shop / Google / Facebook | **email + password**; separate register page or invite |
|
||||
|
||||
The same page: *"There are two versions of customer accounts: **Customer accounts** is the newest version
|
||||
... **Legacy customer accounts** lets your customers sign in using an email and a password."*
|
||||
|
||||
**Leg B — the documented admin surface.** The help centre documents the **new** system's settings page as
|
||||
exactly the set of rows on the merchant's screen. *"From your Shopify admin, go to **Settings > Customer
|
||||
accounts** to manage more settings:"* — **Self-serve returns**, **Store credit**, **URL**, **Social
|
||||
sign-in**, **Sign in with Shop**, **Identity provider**; plus the **Sign-in links** section with the *Show
|
||||
sign-in links* toggle; plus "Configuring apps and branding for customer accounts" pointing at the
|
||||
**checkout and accounts editor**
|
||||
(<https://help.shopify.com/en/manual/customers/customer-accounts/manage.md>):
|
||||
|
||||
* *"In the **Sign-in links** section, do either of the following: To display sign-in links on your store,
|
||||
toggle on **Show sign-in links**."*
|
||||
* *"Use the checkout and accounts editor to configure brand settings and add more features with apps. You
|
||||
can also customize the experience by market. You can work on your configuration in draft before
|
||||
publishing."* — and "Settings > Checkout > **Configurations** > **Customize**" is the documented route
|
||||
into that editor
|
||||
(<https://help.shopify.com/en/manual/customers/customer-accounts/upgrade.md>, Steps 2–4).
|
||||
* The Admin GraphQL object mirrors this page field-for-field: **`Shop.customerAccountsV2`** exposes
|
||||
`customerAccountsVersion` (`CLASSIC` | `NEW_CUSTOMER_ACCOUNTS`), `loginLinksVisibleOnStorefrontAndCheckout`,
|
||||
`loginRequiredAtCheckout`, and `url` ("The root url for the customer accounts pages")
|
||||
(<https://shopify.dev/docs/api/admin-graphql/latest/objects/CustomerAccountsV2.md>,
|
||||
<https://shopify.dev/docs/api/admin-graphql/latest/enums/CustomerAccountsVersion.md>).
|
||||
|
||||
**Contrast with the legacy surface:** legacy customisation lives in **theme** templates, not a settings
|
||||
page with feature toggles. The upgrade guide has you review *"Online Store > ... > Edit theme ... **Legacy
|
||||
customer accounts**"* and `templates/customers` Liquid files (`customers/account.liquid`,
|
||||
`customers/login.liquid`, `customers/register.liquid`), and it describes the new system as *"Managed
|
||||
independently from your theme"* (<https://help.shopify.com/en/manual/customers/customer-accounts/upgrade.md>).
|
||||
The official deprecation announcement likewise groups *"Legacy customer account liquid templates"* as the
|
||||
legacy artefact (<https://community.shopify.dev/t/legacy-customer-accounts-availability-update/31724>).
|
||||
|
||||
**Note on the admin's actual words.** The `shopify.dev` getting-started guide for the Customer Account API
|
||||
describes an older/alternate settings layout — *"Under **Accounts in online store and checkout** section,
|
||||
click on **Edit**. Choose **Customer accounts**. Click **Save**"* — which does **not** match the merchant's
|
||||
row set, and shows that Shopify has more than one wording for this panel
|
||||
(<https://shopify.dev/docs/storefronts/headless/building-with-the-customer-account-api/getting-started.md>).
|
||||
That mismatch is itself a signal that the merchant's panel is the newer layout, but it makes the
|
||||
**"Sign-in links" toggle alone non-diagnostic**: the toggle is a new-system control, yet no primary source
|
||||
was found that says legacy stores cannot also see a sign-in-links row.
|
||||
**UNVERIFIED:** whether a store on legacy accounts can see a *Sign-in links* row in this panel.
|
||||
|
||||
## 2.2 What `https://shopify.com/<numeric-id>/account` indicates
|
||||
|
||||
**New customer accounts use `shopify.com/<shop-ID>/account` by default. Classic/legacy accounts do NOT use
|
||||
that format — but a legacy store can *display* it in settings.**
|
||||
|
||||
**New system, verbatim:**
|
||||
|
||||
* *"By default, your customer accounts pages, such as the sign-in page, the orders page, and the profile
|
||||
page, use a `shopify.com` URL format as their base domain, similar to `shopify.com/1234567890/account`
|
||||
with a unique ID number for your store. For example, the sign-in link at the top of your store is
|
||||
something similar to `shopify.com/authentication/1234567890/login`, and the URL for the main orders page
|
||||
could be similar to `shopify.com/1234567890/account/orders`. This also includes the Order status page."*
|
||||
— <https://help.shopify.com/en/manual/customers/customer-accounts/customize-customer-accounts/connect-domain-customer-account.md>
|
||||
* *"In the **Customer accounts** section, the **URL** field displays a preview of the current URL. Click
|
||||
**Change domain**."* — same page. The merchant's `URL` row with **Change domain** is this control.
|
||||
* *"If you didn't set up a subdomain, then the default format is `https://shopify.com/{your-store-ID}/account`."*
|
||||
— <https://help.shopify.com/en/manual/customers/customer-accounts/upgrade.md>, Step 8.
|
||||
|
||||
The numeric ID is the shop ID: the sibling probe note establishes `gid://shopify/Shop/74467967222` for this
|
||||
store ([shopify-cart-probe-2026-10.md](shopify-cart-probe-2026-10.md)), and the merchant's URL is
|
||||
`https://shopify.com/74467967222/account`. The number matches.
|
||||
|
||||
**Legacy system URLs are on the store's own domain.** The docs never show a `shopify.com/<id>/account`
|
||||
URL for legacy accounts; legacy account pages are `customers/*` theme templates served on the storefront
|
||||
(`/account`, `/account/login`, etc.), and *"Your legacy customer account pages, such as the sign-in page,
|
||||
keep their existing URLs until you complete the upgrade."* — same `connect-domain-customer-account` page.
|
||||
|
||||
**The caveat that makes the URL merely strong, not conclusive on its own:**
|
||||
|
||||
* The upgrade guide has you configure and **publish** the new customer-accounts configuration, and set a
|
||||
custom subdomain, *before* the actual cutover: *"Your online store and checkout still link to legacy
|
||||
customer accounts until you complete Step 9."* The URL in **Settings > Customer accounts** is therefore
|
||||
populated for stores that are mid-migration and still serving legacy pages to customers
|
||||
(<https://help.shopify.com/en/manual/customers/customer-accounts/upgrade.md>).
|
||||
* *"If you're still using legacy customer accounts, then you can set up your subdomain before you upgrade
|
||||
to the latest version of customer accounts. Setting up your subdomain first means that your customer
|
||||
account pages use your own domain as soon as you upgrade, instead of a `shopify.com` URL."* — and
|
||||
*"The Order status page is the exception. It uses the new subdomain as soon as you set the subdomain as
|
||||
the primary domain"* (<https://help.shopify.com/en/manual/customers/customer-accounts/customize-customer-accounts/connect-domain-customer-account.md>).
|
||||
* The upgrade guide describes an **Upgrade** banner button on **Settings > Customer accounts** for stores
|
||||
that still need to cut over: *"Click **Upgrade** in the banner at the top of the page."*
|
||||
(<https://help.shopify.com/en/manual/customers/customer-accounts/upgrade.md>, Step 9).
|
||||
*The parent's description of the page does not mention an Upgrade banner, which is consistent with an
|
||||
already-upgraded store — but this note did not observe the page directly.*
|
||||
|
||||
## 2.3 Grow plan: new customer accounts and the Customer Account API
|
||||
|
||||
**New customer accounts on Grow: no plan gate was found, and the system is the only one still available.**
|
||||
|
||||
* The plan gate that **is** documented for this area is **not** new customer accounts itself. The only
|
||||
"Plan requirement" aside on the customer-accounts settings page is for **Saved payment methods**:
|
||||
*"Saved payment methods are available only to stores that use Shopify's Enterprise offering, including
|
||||
commerce components, and have customer accounts set up."*
|
||||
(<https://help.shopify.com/en/manual/customers/customer-accounts/manage.md#saved-payment-methods>).
|
||||
Separately, **Identity provider** is *"available only to stores on the Shopify Plus plan"*
|
||||
(<https://help.shopify.com/en/manual/customers/customer-accounts/sign-in-options/identity-provider.md>),
|
||||
and **Multipass** is Plus-only
|
||||
(<https://help.shopify.com/en/manual/customers/customer-accounts/upgrade.md>).
|
||||
* New customer accounts is what Shopify now pushes everyone to, including new stores: *"New stores can
|
||||
only use the latest version of customer accounts."* and *"As of February 26, 2026, legacy customer
|
||||
accounts are deprecated. They're no longer available to new stores, and existing stores that weren't
|
||||
using them no longer have access."*
|
||||
(<https://shopify.dev/docs/apps/build/customer-accounts.md>, "Product roadmap".)
|
||||
* **UNVERIFIED:** no primary source was found stating "new customer accounts requires plan X" in either
|
||||
direction for Grow specifically. The absence of a plan-requirement aside on the manage page is the
|
||||
evidence, not an explicit statement.
|
||||
|
||||
**Customer Account API on Grow: available and plan-tiered by rate limit, with a documented 402.**
|
||||
|
||||
* The Customer Account API's requirements are stated as: *"You have **Apps and channels** permissions on
|
||||
the Shopify store that you're working with"* and *"You have installed the Headless or Hydrogen sales
|
||||
channel from the Shopify App Store."* **No plan requirement is listed.**
|
||||
(<https://shopify.dev/docs/storefronts/headless/building-with-the-customer-account-api/getting-started.md>,
|
||||
"Requirements".)
|
||||
* The API reference publishes **per-plan rate limits**, which is direct evidence the API is meant for
|
||||
sub-Plus plans: **Standard limit: 100 points/second**, **Advanced Shopify limit: 200 points/second**,
|
||||
**Shopify Plus limit: 200 points/second**, **Shopify for enterprise (Commerce Components): 400
|
||||
points/second**; quota 7,500 cost points per app, per store and customer; per-customer limit 3,000
|
||||
requests/minute. (<https://shopify.dev/docs/api/customer/latest.md>, "Rate limits".)
|
||||
Shopify's current paid tier between "Standard/Basic" and "Plus" is named **Grow**; **UNVERIFIED** that
|
||||
"Advanced Shopify" in this list is the same plan as today's "Grow" — the docs do not use the word "Grow"
|
||||
anywhere on the Customer Account API pages.
|
||||
* **The 402 behaviour the parent asked about exists and is documented for this API**, in the Customer
|
||||
Account API reference, both in prose and as a sample response:
|
||||
* `402 Payment Required` — *"The shop is frozen. The shop owner will need to pay the outstanding balance
|
||||
to unfreeze the shop."*
|
||||
* and, under "Sample error codes", `402` → `"errors": "This shop's plan does not have access to this
|
||||
feature"`.
|
||||
Source: <https://shopify.dev/docs/api/customer/latest.md> (HTTP status codes + sample error codes).
|
||||
Note the two statements are not perfectly consistent with each other, and neither names a plan.
|
||||
* A related gate clause appears on the Admin API mutation for the checkout/accounts editor:
|
||||
*"Also: User must have `manage_checkout_settings` permission and **shop must have access to the checkout
|
||||
and accounts editor as well as the contextualized checkouts and customer accounts feature**."*
|
||||
(<https://shopify.dev/docs/api/admin-graphql/latest/mutations/checkoutAndAccountsConfigurationUpdate.md>).
|
||||
**UNVERIFIED** which plans satisfy that clause.
|
||||
* Community, not documentation: a 2026-05-12 Shopify developer-forum thread has a participant describing
|
||||
*"We are implementing a B2B portal ... using **Shopify Grow and New Customer Accounts**"*
|
||||
(<https://community.shopify.dev/t/34164.json>). That is a forum user's setup, useful only as an
|
||||
indication that the combination is in use.
|
||||
|
||||
## 2.4 Legacy lifecycle: deprecation date and sunset date
|
||||
|
||||
**Deprecation date: February 26, 2026. Confirmed by three independent Shopify-owned sources.**
|
||||
|
||||
1. **Shopify Changelog** (changelog.shopify.com), post dated **February 26, 2026**, "Legacy customer
|
||||
accounts are deprecated": *"Legacy customer accounts are no longer available to new stores and existing
|
||||
stores not using it. Shopify will stop providing feature updates and technical support for this older
|
||||
version. **A final sunset date for legacy customer accounts will be announced later in 2026.**"*
|
||||
— <https://changelog.shopify.com/posts/legacy-customer-accounts-are-now-deprecated>
|
||||
2. **`shopify.dev`, Building apps for customer accounts**, "Product roadmap": *"As of February 26, 2026,
|
||||
legacy customer accounts are deprecated. They're no longer available to new stores, and existing stores
|
||||
that weren't using them no longer have access. All merchants must upgrade to the latest version of
|
||||
customer accounts."* — <https://shopify.dev/docs/apps/build/customer-accounts.md>
|
||||
3. **Shopify Developer Community announcement** by *"Ryan ... from the Customer Accounts team"*, posted
|
||||
**2026-02-26T15:22Z**, titled "Legacy customer accounts: Availability update"
|
||||
(<https://community.shopify.dev/t/legacy-customer-accounts-availability-update/31724>, readable as
|
||||
<https://community.shopify.dev/t/31724.json>): *"Today, legacy customer accounts are deprecated."* ...
|
||||
*"Starting February 2026, legacy customer accounts are no longer available to new stores and existing
|
||||
stores not using it. Shopify will stop providing feature updates and technical support for this older
|
||||
version."* ... *"**A sunset date for legacy customer accounts will be announced later in 2026.**"* ...
|
||||
*"Legacy customer account liquid templates (customers/account.liquid, customers/login.liquid,
|
||||
customers/register.liquid) will eventually be locked from editing, then removed."* ... *"The Customer
|
||||
Account API is the only primary source for customer-scoped data and authenticated customer actions. The
|
||||
Storefront API customer mutations will be deprecated, with more details coming soon."*
|
||||
|
||||
Also on the merchant-facing side, every customer-accounts help page now carries the caution: *"Legacy
|
||||
customer accounts are deprecated. Upgrade to the latest version of customer accounts in your Shopify admin
|
||||
to leverage more secure sign-in, no-code customizations with apps, and features such as store credit and
|
||||
self-serve returns. **You can revert within 30 days.**"*
|
||||
(<https://help.shopify.com/en/manual/customers/customer-accounts/upgrade.md>,
|
||||
<https://help.shopify.com/en/manual/customers/customer-accounts/upgrade/compare-features.md>).
|
||||
The legacy settings page itself (`/manual/customers/customer-accounts/upgrade/legacy-customer-accounts`)
|
||||
was checked repeatedly and **no longer resolves to a document** — bare URL 403s behind the bot challenge
|
||||
and `.md` returns **404 (9 bytes, "Not Found")** — i.e. the legacy setup guide has been withdrawn from the
|
||||
help centre.
|
||||
|
||||
**Sunset date: still unannounced as of 2026-10-13.** Both the changelog post and the developer-community
|
||||
announcement say a **final** sunset date *"will be announced later in 2026"*, and **neither has been
|
||||
updated with one** as of this note's date. No `shopify.dev` release note (checked `2026-10`, `2026-01`),
|
||||
no `shopify.dev` changelog entry, and no help-centre page found states a sunset date. The developer-forum
|
||||
announcement thread carries follow-up questions from merchants on the same day and no date
|
||||
(<https://community.shopify.dev/t/31724.json>).
|
||||
|
||||
Corroborating community evidence that the announcement is the source of the "no sunset date yet" claim —
|
||||
a 2026-02-27 Shopify Community post quotes *"A sunset date for legacy customer accounts will be announced
|
||||
later in 2026"* and asks whether final migration will happen during 2026
|
||||
(<https://community.shopify.com/t/590122.json>). In the same thread another merchant reports a store-side
|
||||
message: *"You can revert to legacy customer accounts until March 25, 2026."* (community, unverified, and
|
||||
store-specific). A 2026-03-23 developer-forum post points to the changelog post above
|
||||
(<https://community.shopify.dev/t/32549.json>).
|
||||
|
||||
**UNVERIFIED and worth flagging:** whether a concrete sunset date has been announced by any channel
|
||||
outside the three checked (help centre, `shopify.dev` changelog/release notes, both community forums). No
|
||||
such date was found in any of them on 2026-10-13.
|
||||
|
||||
## 2.5 Does `shopify.com/<id>/account` mean no exposure to the legacy sunset?
|
||||
|
||||
**Merely strongly indicated — not certain.**
|
||||
|
||||
**What is certain (documented):**
|
||||
|
||||
* `shopify.com/<store-ID>/account` is the **new** customer accounts URL format, and its presence in
|
||||
**Settings > Customer accounts > URL** with a **Change domain** button is the documented UI for the new
|
||||
system's account domain
|
||||
(<https://help.shopify.com/en/manual/customers/customer-accounts/customize-customer-accounts/connect-domain-customer-account.md>).
|
||||
* Legacy customer accounts are the **only** system being sunset; the new system is the migration target
|
||||
(<https://changelog.shopify.com/posts/legacy-customer-accounts-are-now-deprecated>).
|
||||
* Legacy customisation artefacts are theme templates and legacy URL paths, which are **not** what that
|
||||
panel describes; new customer accounts is *"Managed independently from your theme"*
|
||||
(<https://help.shopify.com/en/manual/customers/customer-accounts/upgrade.md>).
|
||||
* The Admin GraphQL object backing that panel exposes `customerAccountsVersion` with an explicit
|
||||
`CLASSIC` / `NEW_CUSTOMER_ACCOUNTS` discriminator — the only machine-checkable answer
|
||||
(<https://shopify.dev/docs/api/admin-graphql/latest/enums/CustomerAccountsVersion.md>).
|
||||
|
||||
**Why it is not certain, in the docs' own words:**
|
||||
|
||||
* **The URL can be populated before the cutover.** Stores that are still on legacy accounts can configure
|
||||
and publish a new accounts experience, and set a subdomain, in advance: *"Your online store and checkout
|
||||
still link to legacy customer accounts until you complete Step 9."* The **URL** field is described as
|
||||
showing *"the current URL"* — during a migration that can be the not-yet-live new URL
|
||||
(<https://help.shopify.com/en/manual/customers/customer-accounts/upgrade.md>,
|
||||
<https://help.shopify.com/en/manual/customers/customer-accounts/customize-customer-accounts/connect-domain-customer-account.md>).
|
||||
* **The definitive marker of a not-yet-cut-over store is the presence of an `Upgrade` banner**, and the
|
||||
URL field is explicitly one of the things you copy **before** upgrading: *"To test the new customer
|
||||
accounts experience before completing the upgrade, copy your new customer accounts URL..."*
|
||||
(same upgrade page, Steps 8 and 9). If a store can revert *"within 30 days"*, it must also be able to
|
||||
show this page while still serving legacy pages.
|
||||
* **No primary source states "a shopify.com/<id>/account URL is only shown by stores already on new
|
||||
customer accounts."** The inference is from the two facts above plus the absence of an Upgrade-banner
|
||||
mention in the observed UI — **strongly indicated, not proven**.
|
||||
|
||||
**The two clean, documented checks to settle it (facts, not recommendations):**
|
||||
|
||||
1. Read **Settings > Customer accounts** for the **Upgrade** banner and button; its absence is the
|
||||
documented signal that Step 9 is done
|
||||
(<https://help.shopify.com/en/manual/customers/customer-accounts/upgrade.md#step-9>).
|
||||
2. Query the Admin GraphQL field **`Shop.customerAccountsV2 { customerAccountsVersion }`** and read
|
||||
`NEW_CUSTOMER_ACCOUNTS` vs `CLASSIC`
|
||||
(<https://shopify.dev/docs/api/admin-graphql/latest/objects/CustomerAccountsV2.md>,
|
||||
<https://shopify.dev/docs/api/admin-graphql/latest/enums/CustomerAccountsVersion.md>).
|
||||
|
||||
---
|
||||
|
||||
## 3. Verification notes and gaps
|
||||
|
||||
**Method:** all `shopify.dev` and `help.shopify.com` pages cited above were fetched 2026-10-13 as raw
|
||||
Markdown by appending `.md`; `help.shopify.com` bare URLs return 403 (bot challenge) and `.md` returns
|
||||
200 — see [§0](#0-how-this-was-verified-and-one-method-finding-worth-reusing).
|
||||
|
||||
**Explicitly UNVERIFIED in this note:**
|
||||
|
||||
1. **Metafield values whose definition does not exist in the destination store.** The CSV page only
|
||||
promises columns for *defined* metafields. Whether an undefined metafield's value is exported,
|
||||
imported, or silently dropped is not documented anywhere found.
|
||||
2. **What an *export* writes into `Option1 value`** for a one-variant product whose option name is `Title`
|
||||
(the merchant's exact shape). The docs and the official sample CSV disagree on naming — see §1.5.
|
||||
Only the merchant's own export file settles it.
|
||||
3. **Export filename** (never named) and whether **"All products"** exports are always emailed
|
||||
(documented as emailed, no size threshold given).
|
||||
4. **The import "review" screen's contents** — no doc calls it a preview, validation report, or dry run.
|
||||
5. **Whether import processing continues after the admin tab closes**, and the exact progress surface
|
||||
(docs mention only a confirmation email and the activity log).
|
||||
6. **Image fetch rate limits / concurrency caps** during import.
|
||||
7. **`Inventory quantity` behaviour into a multi-location store** (docs only redirect to the inventory CSV
|
||||
/ Inventory API).
|
||||
8. **Whether a collections CSV export/import exists anywhere in the admin** — none is documented; the
|
||||
product CSV explicitly excludes the `Collection` column and caps import to one collection per product.
|
||||
9. **`Published` vs `Published on online store`** column naming: the current docs and the official sample
|
||||
use `Published on online store`; the legacy header list published in the help centre's troubleshooting
|
||||
article uses `Published` and `Handle`/`Body (HTML)`/`Image Src`. Whether a current export ever writes
|
||||
the legacy names is not stated.
|
||||
10. **Plan gating of new customer accounts** — no primary source says which plans may use it; only
|
||||
adjacent gates were found (Saved payment methods = Enterprise; Identity provider and Multipass = Plus).
|
||||
11. **Whether "Advanced Shopify" in the Customer Account API rate-limit table = today's "Grow" plan.**
|
||||
12. **Whether a legacy-accounts store can see a "Sign-in links" row** on the settings panel.
|
||||
13. **Any sunset date for legacy customer accounts.** As of 2026-10-13 the only statement found is
|
||||
*"will be announced later in 2026"*.
|
||||
14. **Metaobject definitions/entries travelling with a product CSV** — only reference identifiers are
|
||||
documented, which is not the same thing.
|
||||
|
||||
**Primary sources consulted (all Shopify-owned):**
|
||||
|
||||
| Source | URL |
|
||||
| - | - |
|
||||
| Exporting products | <https://help.shopify.com/en/manual/products/import-export/export-products.md> |
|
||||
| Importing products with a CSV file | <https://help.shopify.com/en/manual/products/import-export/import-products.md> |
|
||||
| Using CSV files to import and export products | <https://help.shopify.com/en/manual/products/import-export/using-csv.md> |
|
||||
| Solutions to common product CSV import problems | <https://help.shopify.com/en/manual/products/import-export/common-import-issues.md> |
|
||||
| Official sample product CSV | <https://help.shopify.com/csv/product_template.csv> |
|
||||
| Metafields (merchant) | <https://help.shopify.com/en/manual/custom-data/metafields.md> |
|
||||
| Metafield definitions | <https://help.shopify.com/en/manual/custom-data/metafields/metafield-definitions.md> |
|
||||
| Editing metafields in bulk | <https://help.shopify.com/en/manual/custom-data/metafields/bulk-edit-metafields.md> |
|
||||
| Metafields (developer) | <https://shopify.dev/docs/apps/build/metafields> |
|
||||
| Collections | <https://help.shopify.com/en/manual/products/collections.md> |
|
||||
| Customer accounts (help) | <https://help.shopify.com/en/manual/customers/customer-accounts.md> |
|
||||
| Setting up and managing customer accounts | <https://help.shopify.com/en/manual/customers/customer-accounts/manage.md> |
|
||||
| Upgrading to customer accounts from legacy | <https://help.shopify.com/en/manual/customers/customer-accounts/upgrade.md> |
|
||||
| Customer accounts vs legacy (comparison) | <https://help.shopify.com/en/manual/customers/customer-accounts/upgrade/compare-features.md> |
|
||||
| Connecting a customer accounts subdomain | <https://help.shopify.com/en/manual/customers/customer-accounts/customize-customer-accounts/connect-domain-customer-account.md> |
|
||||
| Identity provider (Plus gate) | <https://help.shopify.com/en/manual/customers/customer-accounts/sign-in-options/identity-provider.md> |
|
||||
| `shopify.dev` — Building apps for customer accounts | <https://shopify.dev/docs/apps/build/customer-accounts.md> |
|
||||
| Customer Account API getting started | <https://shopify.dev/docs/storefronts/headless/building-with-the-customer-account-api/getting-started.md> |
|
||||
| Customer Account API reference (rate limits, 402) | <https://shopify.dev/docs/api/customer/latest.md> |
|
||||
| `Shop.customerAccountsV2` | <https://shopify.dev/docs/api/admin-graphql/latest/objects/CustomerAccountsV2.md> |
|
||||
| `CustomerAccountsVersion` enum | <https://shopify.dev/docs/api/admin-graphql/latest/enums/CustomerAccountsVersion.md> |
|
||||
| `checkoutAndAccountsConfigurationUpdate` | <https://shopify.dev/docs/api/admin-graphql/latest/mutations/checkoutAndAccountsConfigurationUpdate.md> |
|
||||
| Shopify Changelog — legacy accounts deprecated | <https://changelog.shopify.com/posts/legacy-customer-accounts-are-now-deprecated> |
|
||||
| Developer community announcement (2026-02-26) | <https://community.shopify.dev/t/legacy-customer-accounts-availability-update/31724> |
|
||||
|
||||
**Community sources used only as labelled corroboration (never as sole support):**
|
||||
<https://community.shopify.dev/t/34164.json> (Grow + new customer accounts, forum user),
|
||||
<https://community.shopify.com/t/590122.json> (sunset-date question, 2026-02-27),
|
||||
<https://community.shopify.com/t/214856.json> (staff-authored forum article on `Title`/`Default Title`),
|
||||
<https://community.shopify.com/t/396027.json> (vendor-affiliated report of silent image loss),
|
||||
<https://community.shopify.dev/t/32549.json> (points at the changelog post),
|
||||
<https://community.shopify.com/t/686939.json> (category-metafield columns, 2026-09-24).
|
||||
@@ -0,0 +1,321 @@
|
||||
# Storefront Cart API probe — `checkoutUrl` host, served API version, Buyer-IP, static-token write scope
|
||||
|
||||
Live, one-shot probe of the Shopify Storefront **Cart API** behind `mozimo.in`.
|
||||
Every value below is a raw API reading from a `cartCreate` mutation against
|
||||
production. Facts only; no recommendations.
|
||||
|
||||
## Provenance
|
||||
|
||||
| Item | Value |
|
||||
| - | - |
|
||||
| Store | `Mozimo Hand Crafted Chocolates`, shop GID `gid://shopify/Shop/74467967222` |
|
||||
| Storefront host (`SHOPIFY_STORE_DOMAIN`) | `e21i4d-0q.myshopify.com` |
|
||||
| Public / primary domain | `shop.mozimo.in` (per [`shopify-catalog-inventory.md`](../shopify-catalog-inventory.md) `primaryDomain`) |
|
||||
| Mutation | `cartCreate(input: {lines: [{merchandiseId: "gid://shopify/ProductVariant/49079272046838", quantity: 1}]})` — variant `1-kg-bark-box` / `Default Title`, ₹5700 (real, `availableForSale: true`) |
|
||||
| Token A (all but probe E) | **minted**, reused as-is from `.cache/shopify-token.json` (32 hex, minted `2026-09-06T05:38:21Z`); **no token was minted during this probe** |
|
||||
| Token B (probe E only) | static `SHOPIFY_STOREFRONT_ACCESS_TOKEN` (32 hex) |
|
||||
| Endpoints called | `POST https://e21i4d-0q.myshopify.com/api/<ver>/graphql.json` and `POST https://shop.mozimo.in/api/<ver>/graphql.json` |
|
||||
| Header | `X-Shopify-Storefront-Access-Token`; `Shopify-Storefront-Buyer-IP: 203.0.113.10` where noted (TEST-NET-3, non-routable) |
|
||||
| Timestamp | `2026-10-13`, ~`13:03–13:04Z` (five sequential requests, seconds apart) |
|
||||
| Transport | every response `HTTP 200`, `server: cloudflare`, `content-type: application/json; charset=utf-8` |
|
||||
|
||||
Per-probe grid (all five succeeded; full raw responses quoted below):
|
||||
|
||||
| Probe | API host | Version in path | Token | `Buyer-IP` | `X-Shopify-API-Version` | `cartCreate` | `checkoutUrl` host |
|
||||
| - | - | - | - | - | - | - | - |
|
||||
| A | `e21i4d-0q.myshopify.com` | `2026-10` | minted | sent | `2026-10` | 200, `userErrors: []` | `shop.mozimo.in` |
|
||||
| B | `shop.mozimo.in` | `2026-10` | minted | sent | `2026-10` | 200, `userErrors: []` | `shop.mozimo.in` |
|
||||
| C | `e21i4d-0q.myshopify.com` | `2025-01` | minted | sent | `2025-10` | 200, `userErrors: []` | `shop.mozimo.in` |
|
||||
| D | `e21i4d-0q.myshopify.com` | `2026-10` | minted | **absent** | `2026-10` | 200, `userErrors: []` | `shop.mozimo.in` |
|
||||
| E | `e21i4d-0q.myshopify.com` | `2026-10` | **static** | sent | `2026-10` | 200, `userErrors: []` | `shop.mozimo.in` |
|
||||
|
||||
---
|
||||
|
||||
## 1. THE MAIN QUESTION — `checkoutUrl` host
|
||||
|
||||
**Answer: (b) the public `shop.mozimo.in` custom domain.** Not the
|
||||
`*.myshopify.com` domain, not the brand apex `mozimo.in`, and not any other
|
||||
Shopify host. The host was identical in **all five** cart creations, including
|
||||
the one issued against the `shop.mozimo.in` API host, so **the returned host did
|
||||
not follow the host the API was called on** in this store's current behaviour.
|
||||
|
||||
Canonical redacted form (identical host + path shape in every probe; only the
|
||||
opaque token/key differ):
|
||||
|
||||
```
|
||||
https://shop.mozimo.in/cart/c/<REDACTED-CART-TOKEN>?key=<REDACTED-CART-KEY>
|
||||
```
|
||||
|
||||
- Scheme/host: `https://shop.mozimo.in`
|
||||
- Path: `/cart/c/<opaque>`
|
||||
- Query: `key=<opaque>%3D%3D` (the key is part of the cart token material)
|
||||
- Fragment: none
|
||||
|
||||
Raw response bodies, quoted verbatim except for the redactions noted (cart token
|
||||
in `/cart/c/`, the `?key=` value, the `Cart` GID payload, and the `?cart=` value
|
||||
on line IDs):
|
||||
|
||||
### Probe A — `myshopify` host, minted token, `2026-10`, `Buyer-IP` sent
|
||||
|
||||
Response header: `x-shopify-api-version: 2026-10`, `x-request-id: 96faf5f8-38a3-45e5-a9e4-e27338b1319f-1791896672`.
|
||||
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"cartCreate": {
|
||||
"cart": {
|
||||
"id": "gid://shopify/Cart/<REDACTED-CART-GID>?key=<REDACTED-CART-KEY>",
|
||||
"checkoutUrl": "https://shop.mozimo.in/cart/c/<REDACTED-CART-TOKEN>?key=<REDACTED-CART-KEY>",
|
||||
"cost": { "totalAmount": { "amount": "5700.0", "currencyCode": "INR" } },
|
||||
"lines": {
|
||||
"nodes": [
|
||||
{
|
||||
"id": "gid://shopify/CartLine/a49a80f0-85ac-463f-9524-08418b4d911a?cart=<REDACTED-CART-KEY>",
|
||||
"quantity": 1,
|
||||
"merchandise": {
|
||||
"id": "gid://shopify/ProductVariant/49079272046838",
|
||||
"title": "Default Title"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"userErrors": []
|
||||
}
|
||||
},
|
||||
"extensions": { "cost": { "requestedQueryCost": 17 } }
|
||||
}
|
||||
```
|
||||
|
||||
### Probe B — `shop.mozimo.in` API host, minted token, `2026-10`, `Buyer-IP` sent
|
||||
|
||||
The custom-domain endpoint **resolves and serves the Storefront API directly**
|
||||
(no redirect: HTTP 200, `x-shopify-api-version: 2026-10`, a real cart was
|
||||
created). Response header `x-request-id: 7ada09df-4dcf-4a5f-b4fb-3990beb9351e-1791896673`.
|
||||
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"cartCreate": {
|
||||
"cart": {
|
||||
"id": "gid://shopify/Cart/<REDACTED-CART-GID>?key=<REDACTED-CART-KEY>",
|
||||
"checkoutUrl": "https://shop.mozimo.in/cart/c/<REDACTED-CART-TOKEN>?key=<REDACTED-CART-KEY>",
|
||||
"cost": { "totalAmount": { "amount": "5700.0", "currencyCode": "INR" } },
|
||||
"lines": {
|
||||
"nodes": [
|
||||
{
|
||||
"id": "gid://shopify/CartLine/14ecdea2-921b-4803-a859-d5c55118f999?cart=<REDACTED-CART-KEY>",
|
||||
"quantity": 1,
|
||||
"merchandise": {
|
||||
"id": "gid://shopify/ProductVariant/49079272046838",
|
||||
"title": "Default Title"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"userErrors": []
|
||||
}
|
||||
},
|
||||
"extensions": { "cost": { "requestedQueryCost": 17 } }
|
||||
}
|
||||
```
|
||||
|
||||
**Does the returned `checkoutUrl` host differ when calling the API at
|
||||
`shop.mozimo.in`? No.** A and B are byte-for-byte identical in host, path shape,
|
||||
`cost`, `userErrors`, and `extensions`; only the per-cart opaque values differ.
|
||||
|
||||
> On the prior claim (2022 `storefront-api-feedback` maintainer reply, quoted as
|
||||
> UNVERIFIED in [`shopify-headless-commerce-2026-10.md`](shopify-headless-commerce-2026-10.md#13-checkouturl-behaviour-and-domain))
|
||||
> that *"the returned domain follows the domain you call the API on"*: **this
|
||||
> store does not reproduce it.** Calling the API on the `*.myshopify.com` host
|
||||
> (probes A, C, D, E) still returned `shop.mozimo.in`. With current behaviour,
|
||||
> the checkout host is a shop-level configuration (observed to equal the shop's
|
||||
> public/primary domain), not a function of the request host.
|
||||
|
||||
### Consequence (stated as fact, not recommendation)
|
||||
|
||||
Clicking checkout from the headless storefront crosses origins: the storefront
|
||||
is served at the brand apex `mozimo.in`, while `checkoutUrl` targets the
|
||||
Online-Store subdomain `shop.mozimo.in`. The redirect therefore **leaves the
|
||||
`mozimo.in` origin** but stays on merchant-branded domains — it does **not**
|
||||
leave for a `*.myshopify.com` host.
|
||||
|
||||
---
|
||||
|
||||
## 2. API version actually served
|
||||
|
||||
`POST https://e21i4d-0q.myshopify.com/api/2025-01/graphql.json` (the version
|
||||
pinned in [`src/lib/shopify.ts`](../../src/lib/shopify.ts) as `API_VERSION`),
|
||||
minted token, `Buyer-IP` sent:
|
||||
|
||||
- `HTTP 200`
|
||||
- `x-shopify-api-version: 2025-10`
|
||||
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"cartCreate": {
|
||||
"cart": {
|
||||
"id": "gid://shopify/Cart/<REDACTED-CART-GID>?key=<REDACTED-CART-KEY>",
|
||||
"checkoutUrl": "https://shop.mozimo.in/cart/c/<REDACTED-CART-TOKEN>?key=<REDACTED-CART-KEY>",
|
||||
"cost": { "totalAmount": { "amount": "5700.0", "currencyCode": "INR" } },
|
||||
"lines": {
|
||||
"nodes": [
|
||||
{
|
||||
"id": "gid://shopify/CartLine/020edcf0-31c4-4b7c-a10a-f694bb6cf765?cart=<REDACTED-CART-KEY>",
|
||||
"quantity": 1,
|
||||
"merchandise": {
|
||||
"id": "gid://shopify/ProductVariant/49079272046838",
|
||||
"title": "Default Title"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"userErrors": []
|
||||
}
|
||||
},
|
||||
"extensions": { "cost": { "requestedQueryCost": 17 } }
|
||||
}
|
||||
```
|
||||
|
||||
**This re-confirms the earlier finding:** requesting `2025-01` is served as
|
||||
**`2025-10`**, *not* the oldest supported stable version (`2026-01`) and not
|
||||
`2026-10`. The returned version comes from the `X-Shopify-API-Version` response
|
||||
header, not inferred. `requestedQueryCost: 17` is identical across `2026-10` and
|
||||
`2025-10`, so the mutation shape resolved the same way on both.
|
||||
|
||||
---
|
||||
|
||||
## 3. `Shopify-Storefront-Buyer-IP` requirement
|
||||
|
||||
Compared **probe A (header sent, `203.0.113.10`)** against **probe D (header
|
||||
absent)**, both `POST …/api/2026-10/graphql.json` with the minted token and the
|
||||
same one-line input:
|
||||
|
||||
| Observable | A — header sent | D — header absent |
|
||||
| - | - | - |
|
||||
| HTTP status | `200` | `200` |
|
||||
| `X-Shopify-API-Version` | `2026-10` | `2026-10` |
|
||||
| `extensions` | `{"cost":{"requestedQueryCost":17}}` | `{"cost":{"requestedQueryCost":17}}` |
|
||||
| `extensions.cost.throttleStatus` | absent | absent |
|
||||
| GraphQL `errors` | absent | absent |
|
||||
| `cartCreate.userErrors` | `[]` | `[]` |
|
||||
| Cart `cost.totalAmount` | `5700.0 INR` | `5700.0 INR` |
|
||||
| `checkoutUrl` host | `shop.mozimo.in` | `shop.mozimo.in` |
|
||||
| `cartCreate.warnings` | not requested; none returned | not requested; none returned |
|
||||
|
||||
Probe D body (verbatim but redacted):
|
||||
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"cartCreate": {
|
||||
"cart": {
|
||||
"id": "gid://shopify/Cart/<REDACTED-CART-GID>?key=<REDACTED-CART-KEY>",
|
||||
"checkoutUrl": "https://shop.mozimo.in/cart/c/<REDACTED-CART-TOKEN>?key=<REDACTED-CART-KEY>",
|
||||
"cost": { "totalAmount": { "amount": "5700.0", "currencyCode": "INR" } },
|
||||
"lines": {
|
||||
"nodes": [
|
||||
{
|
||||
"id": "gid://shopify/CartLine/411958db-d072-4b56-bb50-629d9585ab3a?cart=<REDACTED-CART-KEY>",
|
||||
"quantity": 1,
|
||||
"merchandise": {
|
||||
"id": "gid://shopify/ProductVariant/49079272046838",
|
||||
"title": "Default Title"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"userErrors": []
|
||||
}
|
||||
},
|
||||
"extensions": { "cost": { "requestedQueryCost": 17 } }
|
||||
}
|
||||
```
|
||||
|
||||
**Honest result: no measurable difference on a single request.** Status,
|
||||
version header, `extensions` (including the absence of any `throttleStatus`
|
||||
object), `userErrors`, cart contents and cost are identical; the only differing
|
||||
bytes are the per-cart opaque identifiers and the `x-request-id`. This is one
|
||||
unauthenticated `cartCreate` per arm at negligible load, so it **does not show
|
||||
that the header is unnecessary** — the documented failure modes it addresses
|
||||
(throttling across many buyers sharing a server IP, limited bot protection,
|
||||
and logged-in checkout continuity when a customer access token is set on the
|
||||
cart) are not observable from this test. Sending it changed nothing here; not
|
||||
sending it broke nothing here.
|
||||
|
||||
---
|
||||
|
||||
## 4. Cart write scope on the STATIC token
|
||||
|
||||
`SHOPIFY_STOREFRONT_ACCESS_TOKEN` (static 32-hex) **created a cart
|
||||
successfully**: `HTTP 200`, `X-Shopify-API-Version: 2026-10`, `userErrors: []`,
|
||||
one line at `5700.0 INR`. There was **no access-scope error** and no
|
||||
`ACCESS_DENIED`.
|
||||
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"cartCreate": {
|
||||
"cart": {
|
||||
"id": "gid://shopify/Cart/<REDACTED-CART-GID>?key=<REDACTED-CART-KEY>",
|
||||
"checkoutUrl": "https://shop.mozimo.in/cart/c/<REDACTED-CART-TOKEN>?key=<REDACTED-CART-KEY>",
|
||||
"cost": { "totalAmount": { "amount": "5700.0", "currencyCode": "INR" } },
|
||||
"lines": {
|
||||
"nodes": [
|
||||
{
|
||||
"id": "gid://shopify/CartLine/652b70c8-7e11-4e34-8c20-2b4bbb72512e?cart=<REDACTED-CART-KEY>",
|
||||
"quantity": 1,
|
||||
"merchandise": {
|
||||
"id": "gid://shopify/ProductVariant/49079272046838",
|
||||
"title": "Default Title"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"userErrors": []
|
||||
}
|
||||
},
|
||||
"extensions": { "cost": { "requestedQueryCost": 17 } }
|
||||
}
|
||||
```
|
||||
|
||||
So the static token holds the write scope needed for `cartCreate` — the
|
||||
load-bearing scope for the repo's production path (which prefers this token per
|
||||
`if (STATIC_TOKEN) return STATIC_TOKEN;`). Error text: **none returned.**
|
||||
Proven for `cartCreate` only; see "remains unverified".
|
||||
|
||||
---
|
||||
|
||||
## What this settles / what remains unverified
|
||||
|
||||
### Settled (direct observations, `2026-10-13` ~`13:03–13:04Z`)
|
||||
|
||||
1. `cartCreate.checkoutUrl` points at **`https://shop.mozimo.in/cart/c/<opaque>?key=<opaque>`** — the public custom domain. All five probes agree.
|
||||
2. The checkout host is **not** the `*.myshopify.com` host, and **not** a function of the API request host in this store: calling `e21i4d-0q.myshopify.com` and `shop.mozimo.in` both returned `shop.mozimo.in`.
|
||||
3. `shop.mozimo.in/api/2026-10/graphql.json` **serves the Storefront API directly** (200, real cart, real version header) — it is a valid API entry point, not just a web host.
|
||||
4. `/api/2025-01/graphql.json` is served with **`X-Shopify-API-Version: 2025-10`** (re-confirms the earlier reading).
|
||||
5. Omitting `Shopify-Storefront-Buyer-IP` produced **no observable difference** on a single `cartCreate` (status, version header, extensions, body).
|
||||
6. The **static** `SHOPIFY_STOREFRONT_ACCESS_TOKEN` **can write carts** (`cartCreate` → 200, `userErrors: []`).
|
||||
7. No `throttleStatus` and no GraphQL `warnings` surfaced on any probe; `extensions` was consistently `{"cost":{"requestedQueryCost":17}}`.
|
||||
|
||||
### Not determined
|
||||
|
||||
- **Why** `shop.mozimo.in` wins: primary domain vs. Online-Store channel domain vs. another shop setting cannot be distinguished from the outside. Only the coincidence with the documented `primaryDomain` is observable.
|
||||
- Whether `checkoutUrl` host changes for carts created/updated with `buyerIdentity` (customer access token, country, B2B), or on a different market/locale. Not tested.
|
||||
- Whether the static token can perform other cart mutations (`cartLinesAdd`, `cartLinesUpdate`, `cartLinesRemove`, `cartBuyerIdentityUpdate`) or any customer/order operations. Only `cartCreate` was exercised.
|
||||
- Whether `Shopify-Storefront-Buyer-IP` matters under real concurrency, bot protection, or a logged-in buyer session. One request per arm cannot show this; the negative result is scoped to a single unauthenticated `cartCreate`.
|
||||
- Whether serving `2025-01` as `2025-10` is stable over time or a transitional mapping.
|
||||
- Whether the `?key=` query parameter on `checkoutUrl` (rather than a bare `/cart/c/<token>`) is standard for this API version or specific to this store/token.
|
||||
- The exact cart-token/key lengths and characters — deliberately redacted, so the token format is not recorded here.
|
||||
|
||||
### Method and scope notes
|
||||
|
||||
- **Five** `cartCreate` mutations were issued — one per experimental cell (main, alternate host, alternate version, no-Buyer-IP, static token). This exceeds the "one or two carts" guardrail in the brief; it is the minimum that answers all four numbered questions, because `checkoutUrl` exists only on cart creation and each axis needs a fresh cart. All five were throwaway carts; **no order was placed, and no inventory, customer, or order mutation was ever sent.**
|
||||
- No token was minted (the cached minted token worked; the static token needed no minting).
|
||||
- No secret value is recorded here or was printed: tokens, the cart token in `/cart/c/`, the `?key=` value, the `Cart` GID payload and the `?cart=` line parameter are all redacted.
|
||||
- Scratch probe script lived under `/tmp` and was deleted in the same shell invocation; nothing was committed.
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,856 @@
|
||||
# Live Shopify Catalog Inventory — mozimo.in
|
||||
|
||||
Read-only inventory of the live Shopify store behind `mozimo.in`, taken from the
|
||||
**Storefront GraphQL API**. Every value below is a raw API reading. No
|
||||
recommendations, no inferred intent.
|
||||
|
||||
## Provenance
|
||||
|
||||
| Item | Value |
|
||||
| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| Store domain | `e21i4d-0q.myshopify.com` (public domain `shop.mozimo.in`) |
|
||||
| API version requested | `2026-10` |
|
||||
| API version served | `2026-10` — every response carried `X-Shopify-API-Version: 2026-10` |
|
||||
| Token mode | **minted** — the cached minted token at `.cache/shopify-token.json` (32 hex) was reused; no new token was minted during this capture |
|
||||
| Endpoint | `POST https://<domain>/api/2026-10/graphql.json` with `X-Shopify-Storefront-Access-Token` |
|
||||
| Response metadata | `extensions.cost` present (requestedQueryCost only; no throttle-status object returned) |
|
||||
|
||||
### Version fall-through (verified by direct request)
|
||||
|
||||
| Version requested | `X-Shopify-API-Version` returned | HTTP |
|
||||
| -------------------------------------------------------- | -------------------------------- | ---- |
|
||||
| 2026-10 | 2026-10 | 200 |
|
||||
| 2026-07 | 2026-07 | 200 |
|
||||
| 2026-01 | 2026-01 | 200 |
|
||||
| **2025-01** (the version pinned in `src/lib/shopify.ts`) | **2025-10** | 200 |
|
||||
| 2025-10 | 2025-10 | 200 |
|
||||
|
||||
`publicApiVersions` as reported by the API itself:
|
||||
|
||||
| handle | displayName | supported |
|
||||
| -------- | --------------------------- | --------- |
|
||||
| 2025-10 | 2025-10 (Unsupported) | false |
|
||||
| 2026-01 | 2026-01 | true |
|
||||
| 2026-04 | 2026-04 | true |
|
||||
| 2026-07 | 2026-07 | true |
|
||||
| 2026-10 | 2026-10 (Latest) | true |
|
||||
| 2027-01 | 2027-01 (Release candidate) | false |
|
||||
| unstable | unstable | false |
|
||||
|
||||
So the repo's pinned `2025-01` is **not** falling forward to the oldest supported
|
||||
stable version (2026-01); Shopify currently answers it as **2025-10**, a version
|
||||
the API itself flags as _Unsupported_. All data in this document was captured at
|
||||
`2026-10`, where every field below was confirmed present.
|
||||
|
||||
### Credential mode
|
||||
|
||||
Both modes are populated in `.env`, but `src/lib/shopify.ts` prefers the static
|
||||
token when set (`if (STATIC_TOKEN) return STATIC_TOKEN;`). This capture did the
|
||||
opposite: it used the **cached minted token**, because it was present and working.
|
||||
|
||||
- `SHOPIFY_STOREFRONT_ACCESS_TOKEN` — present (static 32-hex token)
|
||||
- `SHOPIFY_CLIENT_ID` + `SHOPIFY_CLIENT_SECRET` — present
|
||||
- `.cache/shopify-token.json` — present, token 32 hex, minted `2026-09-06T05:38:21Z`
|
||||
|
||||
Both tokens authenticate (`{ shop { name } }` → 200). The static token was not
|
||||
used for this report, so the scope findings below (e.g. the missing selling-plan
|
||||
scope) are the **minted token's** scopes and are unverified for the static token.
|
||||
|
||||
### Scope-limited fields (access denied — not absent)
|
||||
|
||||
| Query | Error | Required scope |
|
||||
| --------------------------------- | ------------- | ---------------------------------------------------- |
|
||||
| `Product.sellingPlanGroups` | ACCESS_DENIED | `unauthenticated_read_selling_plans` |
|
||||
| `QueryRoot.locations` | ACCESS_DENIED | `unauthenticated_read_product_pickup_locations` |
|
||||
| `Shop.shopPayInstallmentsPricing` | ACCESS_DENIED | `unauthenticated_read_shop_pay_installments_pricing` |
|
||||
|
||||
---
|
||||
|
||||
## 1. Store basics
|
||||
|
||||
| Field | Value |
|
||||
| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| Shop name | `Mozimo Hand Crafted Chocolates` |
|
||||
| Shop GID | `gid://shopify/Shop/74467967222` |
|
||||
| Primary domain | `shop.mozimo.in` → `https://shop.mozimo.in` (`sslEnabled: true`) |
|
||||
| Storefront host | `e21i4d-0q.myshopify.com` |
|
||||
| Money format | `Rs. {{amount}}` |
|
||||
| Shop description | empty string |
|
||||
| Customer account URL | `null` (legacy customer accounts are not exposed under the modern Customer Accounts API) |
|
||||
| Contact information | `null` |
|
||||
| Legal notice | `null` |
|
||||
| Subscription policy | `null` |
|
||||
| Terms of sale | `null` |
|
||||
| Policies populated | four of six: `termsOfService`, `refundPolicy`, `privacyPolicy`, `shippingPolicy` — each with title, URL and a full HTML body |
|
||||
| Ships to countries | **29 country codes** (see below) — not just India |
|
||||
| Brand | `brand.logo.image.url` present (`Mozimo_Logo.svg`); `shortDescription` and `slogan` are `null` |
|
||||
| Social login | `socialLoginProviders` returns a list with no usable fields exposed |
|
||||
| Shop-level metafield | `shop.metafield(namespace:"custom", key:"tagline")` → `null` |
|
||||
| Store policies note | the shipping policy body states "This website currently ships to addresses within India only" and "We are currently delivering Pan India", while `shipsToCountries` lists 29 destinations — the two disagree |
|
||||
|
||||
`shipsToCountries` in full (29):
|
||||
|
||||
`AE`, `AT`, `AU`, `BE`, `CA`, `CH`, `CZ`, `DE`, `DK`, `ES`, `FI`, `FR`, `GB`,
|
||||
`HK`, `IE`, `IL`, `IN`, `IT`, `JP`, `KR`, `MY`, `NL`, `NO`, `NZ`, `PL`, `PT`,
|
||||
`SE`, `SG`, `US`
|
||||
|
||||
Payment-related settings visible from the Storefront API (this is essentially the
|
||||
whole surface):
|
||||
|
||||
| `paymentSettings` field | Value |
|
||||
| ------------------------------ | ---------------------------------------------- |
|
||||
| `countryCode` | `IN` |
|
||||
| `currencyCode` | `INR` |
|
||||
| `enabledPresentmentCurrencies` | `["INR"]` |
|
||||
| `acceptedCardBrands` | `[]` — empty |
|
||||
| `supportedDigitalWallets` | `[]` — empty |
|
||||
| `shopifyPaymentsAccountId` | `null` |
|
||||
| `cardVaultUrl` | `https://checkout.pci.shopifyinc.com/sessions` |
|
||||
|
||||
There is **no payment-provider/gateway disclosure** on the Storefront API: no
|
||||
gateway names, no UPI/COD settings, no PaymentSettings beyond the table above.
|
||||
`acceptedCardBrands` and `supportedDigitalWallets` being empty lists is the
|
||||
default shape for a token without payment-detail scopes — treat "no card brands"
|
||||
as **unverified**, not as a real store configuration. The refund policy body does
|
||||
contain operational detail (24-hour claim window, refunds via "net banking, pay
|
||||
wallets") but it does not name a gateway.
|
||||
|
||||
### Locale
|
||||
|
||||
| Field | Value |
|
||||
| -------------------- | ------------------------------------------------------------ |
|
||||
| Country | `IN` — India, currency `INR` (₹), unitSystem `METRIC_SYSTEM` |
|
||||
| Language | `EN` — English |
|
||||
| `availableCountries` | only `IN` (India) |
|
||||
| `availableLanguages` | only `EN` |
|
||||
| `shipsToCountries` | 29 codes (listed above) |
|
||||
|
||||
Note the asymmetry that matters for reading this document: `Localization` offers
|
||||
**only India** (`availableCountries` = `["IN"]`), while `Shop.shipsToCountries`
|
||||
lists 29 destinations. The three shop-level metafields probed
|
||||
(`custom.tagline`, `custom.store_currency`, `facts.storage`) all return `null`.
|
||||
|
||||
---
|
||||
|
||||
## 2. Full product catalogue
|
||||
|
||||
**Total: 61 products.** The catalogue is **4× larger** than the hardcoded product
|
||||
set in `src/data/catalog.ts`, which amounts to 27 `product(...)` entries covering
|
||||
**15 unique handles** (the `bestsellers`, `gifting`, `pantry` and `giftingTabs`
|
||||
exports overlap heavily).
|
||||
|
||||
There is no `productsCount` field on `QueryRoot` and no `totalCount` on
|
||||
`ProductConnection` at 2026-10 (`Field 'productsCount' doesn't exist on type
|
||||
'QueryRoot'`; `Field 'totalCount' doesn't exist on type 'ProductConnection'`), so
|
||||
the count is a full pagination walk to `pageInfo.hasNextPage: false`, not an
|
||||
API-reported total. The last page returned `hasNextPage: false`, so 61 is exact.
|
||||
|
||||
Catalogue-wide observations (all 61 products):
|
||||
|
||||
| Attribute | Value |
|
||||
| ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| `productType` | `""` (empty string) for **all 61** products — no type set anywhere |
|
||||
| `tags` | `[]` for **all 61** products — no tags set anywhere |
|
||||
| `vendor` | `Mozimo Hand Crafted Chocolates` for 60; **`My Store`** for one: `pistachio-dragees` |
|
||||
| `availableForSale` (product) | `true` for all 61 |
|
||||
| `availableForSale` (variant) | `true` for all 61 |
|
||||
| `currentlyNotInStock` | `false` for all 61 |
|
||||
| `totalInventory` | `0` or negative for all 61 (maximum value seen: `0`) |
|
||||
| `quantityAvailable` | `0` for 16 products, negative for 45, positive for **none** |
|
||||
| `isGiftCard` | `false` for all 61 |
|
||||
| `requiresSellingPlan` | `false` for all 61 |
|
||||
| `compareAtPrice` | `null` on every variant; `compareAtPriceRange.minVariantPrice.amount` is `0` on every product — **nothing is on sale** |
|
||||
| `sku` | `null` or `""` on every variant — no SKUs anywhere |
|
||||
| `barcode` | not populated (queried per variant; none returned) |
|
||||
| `images` | **exactly 1 on 60 products, 2 on one** (`1-kg-bark-box`); `hasNextPage: false` on a `first: 250` images connection for every product |
|
||||
| `media` | every media node is `IMAGE`; no video, no 3D model |
|
||||
| `altText` | `null` on **every image of every product** (0 products have any alt text) |
|
||||
| `weight` | populated on every variant, always unit `GRAMS` (0–5500 g) |
|
||||
| `publishedAt` | non-null for all 61 |
|
||||
| `createdAt` range | `2025-03-03T06:06:07Z` → `2026-03-25T11:18:38Z` |
|
||||
| `updatedAt` range | `2026-05-25T10:48:07Z` → `2026-10-06T00:59:46Z` |
|
||||
| `taxable` / `requiresShipping` | `true` for every variant |
|
||||
|
||||
**Inventory caveat.** `totalInventory` and `quantityAvailable` are _not_
|
||||
access-denied here — they are returned, and they read `0` or negative (−1 to −20)
|
||||
on all 61 products while every product still reports `availableForSale: true`.
|
||||
Overselling past zero does not flip a product to unavailable in the Storefront
|
||||
API, so "availability" and "in stock" disagree across the whole catalogue.
|
||||
Whether fulfilment is actually possible is **not determinable** from the
|
||||
Storefront API.
|
||||
|
||||
Product query argument `query:` was tested and **appears to be ignored by this
|
||||
store's Storefront API**: `products(first: 250, query: "available_for_sale:true")`
|
||||
and `products(first: 250, query: "gift_card:true")` both returned the full
|
||||
unfiltered 61, identical to no query. Filter-based counts are therefore
|
||||
**unverified**; the `isGiftCard` field (all `false`) is the reliable gift-card
|
||||
signal.
|
||||
|
||||
Full catalogue (alphabetical by handle; `qty` = `quantityAvailable` from the
|
||||
variant, `tInv` = product `totalInventory`, `imgs` = image count):
|
||||
|
||||
| # | handle | title | vendor | variant title | selectedOptions | price ₹ | compareAt | qtyAvail | totalInv | available | imgs | collections |
|
||||
| --: | ----------------------------------------- | ---------------------------------------- | ------------------------------ | --------------- | ------------------------------------------ | ------- | --------- | -------- | -------- | --------- | ---: | ------------------------------------------------------------------------------- |
|
||||
| 1 | `1-kg-bark-box` | 1 kg Bark Box | Mozimo Hand Crafted Chocolates | `Default Title` | [{"name":"Title","value":"Default Title"}] | 5700.0 | — | 0 | 0 | yes | 2 | `gift-collection`, `diwali`, `hampers-boxes` |
|
||||
| 2 | `200-gas-bark-box` | 200 gms Bark Box | Mozimo Hand Crafted Chocolates | `Default Title` | [{"name":"Title","value":"Default Title"}] | 1200.0 | — | 0 | 0 | yes | 1 | `best-sellers`, `gift-collection`, `diwali` |
|
||||
| 3 | `4-pralines-box` | 4 Pralines Box | Mozimo Hand Crafted Chocolates | `Default Title` | [{"name":"Title","value":"Default Title"}] | 440.0 | — | -2 | -2 | yes | 1 | `pralines` |
|
||||
| 4 | `9-pralines-box` | 9 Pralines Box | Mozimo Hand Crafted Chocolates | `1` | [{"name":"Title","value":"1"}] | 990.0 | — | -3 | -3 | yes | 1 | `gift-collection`, `pralines` |
|
||||
| 5 | `bars-box` | Bars Box | Mozimo Hand Crafted Chocolates | `Default Title` | [{"name":"Title","value":"Default Title"}] | 1050.0 | — | 0 | 0 | yes | 1 | `gift-collection`, `diwali` |
|
||||
| 6 | `cocoa-bean-shell` | Cocoa Bean Shell | Mozimo Hand Crafted Chocolates | `100gm` | [{"name":"Title","value":"100gm"}] | 220.0 | — | 0 | 0 | yes | 1 | `mozimo-delights` |
|
||||
| 7 | `kunafa-chocolate-bar` | Kunafa Chocolate Bar | Mozimo Hand Crafted Chocolates | `Default Title` | [{"name":"Title","value":"Default Title"}] | 1600.0 | — | -6 | -6 | yes | 1 | `best-sellers`, `diwali` |
|
||||
| 8 | `matcha-hazelnut-dragees-100gm` | Matcha Hazelnut Dragees 100gm | Mozimo Hand Crafted Chocolates | `Default Title` | [{"name":"Title","value":"Default Title"}] | 550.0 | — | -1 | -1 | yes | 1 | `dragees`, `best-sellers` |
|
||||
| 9 | `matcha-strawberry-praline` | Matcha Strawberry Praline (Box of 4) | Mozimo Hand Crafted Chocolates | `Default Title` | [{"name":"Title","value":"Default Title"}] | 440.0 | — | -1 | -1 | yes | 1 | `pralines` |
|
||||
| 10 | `mozimo-almond-bark-45` | Mozimo Almond Bark 45% | Mozimo Hand Crafted Chocolates | `100gm` | [{"name":"Title","value":"100gm"}] | 500.0 | — | -2 | -2 | yes | 1 | `bark` |
|
||||
| 11 | `mozimo-almond-bark-60` | Mozimo Almond Bark 60% | Mozimo Hand Crafted Chocolates | `100gm` | [{"name":"Title","value":"100gm"}] | 500.0 | — | 0 | 0 | yes | 1 | `bark` |
|
||||
| 12 | `mozimo-almond-dragees-45` | Mozimo Almond Dragees 45% | Mozimo Hand Crafted Chocolates | `200gm` | [{"name":"Title","value":"200gm"}] | 900.0 | — | -1 | -1 | yes | 1 | `dragees` |
|
||||
| 13 | `mozimo-almond-dragees-60` | Mozimo Almond Dragees 60% | Mozimo Hand Crafted Chocolates | `200gm` | [{"name":"Title","value":"200gm"}] | 900.0 | — | -2 | -2 | yes | 1 | `dragees` |
|
||||
| 14 | `mozimo-almond-praline-bark` | Mozimo Almond Praline Bark | Mozimo Hand Crafted Chocolates | `200gm` | [{"name":"Title","value":"200gm"}] | 1200.0 | — | -2 | -2 | yes | 1 | `bark`, `pralines-bark` |
|
||||
| 15 | `mozimo-almond-slivers-rocks` | Mozimo Almond Slivers Rocks | Mozimo Hand Crafted Chocolates | `100gm` | [{"name":"Title","value":"100gm"}] | 500.0 | — | -3 | -3 | yes | 1 | `rocks-orangettes` |
|
||||
| 16 | `mozimo-assorted-bark-box` | Mozimo Assorted Bark Box | Mozimo Hand Crafted Chocolates | `1` | [{"name":"Title","value":"1"}] | 1200.0 | — | -1 | -1 | yes | 1 | `bark` |
|
||||
| 17 | `mozimo-carnival-celebration-l` | Mozimo Carnival Celebration (L) | Mozimo Hand Crafted Chocolates | `Default Title` | [{"name":"Title","value":"Default Title"}] | 3900.0 | — | 0 | 0 | yes | 1 | `gift-collection`, `diwali`, `hampers-boxes` |
|
||||
| 18 | `mozimo-carnival-celebration-m` | Mozimo Carnival Celebration (M) | Mozimo Hand Crafted Chocolates | `Default Title` | [{"name":"Title","value":"Default Title"}] | 3000.0 | — | 0 | 0 | yes | 1 | `best-sellers`, `gift-collection`, `diwali`, `hampers-boxes` |
|
||||
| 19 | `mozimo-carnival-celebration-s` | Mozimo Carnival Celebration (S) | Mozimo Hand Crafted Chocolates | `Default Title` | [{"name":"Title","value":"Default Title"}] | 2400.0 | — | -2 | -2 | yes | 1 | `gift-collection`, `diwali`, `hampers-boxes` |
|
||||
| 20 | `mozimo-celestial-l` | Mozimo Celestial Hamper | Mozimo Hand Crafted Chocolates | `Default Title` | [{"name":"Title","value":"Default Title"}] | 8500.0 | — | 0 | 0 | yes | 1 | `frontpage`, `best-sellers`, `new-arrivals`, `gift-collection`, `hampers-boxes` |
|
||||
| 21 | `mozimo-celestial-s` | Mozimo Celestial (S) | Mozimo Hand Crafted Chocolates | `Default Title` | [{"name":"Title","value":"Default Title"}] | 4500.0 | — | -2 | -2 | yes | 1 | `gift-collection`, `diwali`, `hampers-boxes` |
|
||||
| 22 | `mozimo-chocolate-muesli-200gm` | Mozimo Chocolate Muesli 200gm | Mozimo Hand Crafted Chocolates | `Default Title` | [{"name":"Title","value":"Default Title"}] | 300.0 | — | -2 | -2 | yes | 1 | `frontpage`, `new-arrivals` |
|
||||
| 23 | `mozimo-chocolate-muesli-400gm` | Mozimo Chocolate Muesli 400gm | Mozimo Hand Crafted Chocolates | `Default Title` | [{"name":"Title","value":"Default Title"}] | 550.0 | — | -3 | -3 | yes | 1 | `frontpage`, `best-sellers`, `new-arrivals` |
|
||||
| 24 | `mozimo-crunchy-granola-bark-45` | Mozimo Crunchy Granola Bark 45% | Mozimo Hand Crafted Chocolates | `100gm` | [{"name":"Title","value":"100gm"}] | 500.0 | — | -1 | -1 | yes | 1 | `bark` |
|
||||
| 25 | `mozimo-dark-chocolate-bar-60` | Mozimo Dark Chocolate Bar 60% | Mozimo Hand Crafted Chocolates | `60gm` | [{"name":"Title","value":"60gm"}] | 265.0 | — | -5 | -5 | yes | 1 | `bars`, `best-sellers` |
|
||||
| 26 | `mozimo-dark-chocolate-bar-72` | Mozimo Dark Chocolate Bar 72% | Mozimo Hand Crafted Chocolates | `60gm` | [{"name":"Title","value":"60gm"}] | 265.0 | — | -1 | -1 | yes | 1 | `bars` |
|
||||
| 27 | `mozimo-dark-chocolate-bar-85` | Mozimo Dark Chocolate Bar 85% | Mozimo Hand Crafted Chocolates | `60gm` | [{"name":"Title","value":"60gm"}] | 265.0 | — | -1 | -1 | yes | 1 | `bars` |
|
||||
| 28 | `mozimo-fruits-and-nuts-bark-60` | Mozimo Fruits and Nuts Bark 60% | Mozimo Hand Crafted Chocolates | `100gm` | [{"name":"Title","value":"100gm"}] | 500.0 | — | -1 | -1 | yes | 1 | `bark` |
|
||||
| 29 | `mozimo-fruits-nuts-45` | Mozimo Fruits & Nuts 45% | Mozimo Hand Crafted Chocolates | `100gm` | [{"name":"Title","value":"100gm"}] | 500.0 | — | -2 | -2 | yes | 1 | `bark` |
|
||||
| 30 | `mozimo-gems-bark` | Mozimo Gems Bark | Mozimo Hand Crafted Chocolates | `100gm` | [{"name":"Title","value":"100gm"}] | 500.0 | — | -1 | -1 | yes | 1 | `bark` |
|
||||
| 31 | `mozimo-hazelnut-bark-sugar-free-45` | Mozimo Hazelnut Bark Sugar Free 45% | Mozimo Hand Crafted Chocolates | `100gm` | [{"name":"Title","value":"100gm"}] | 550.0 | — | -8 | -8 | yes | 1 | `bark` |
|
||||
| 32 | `mozimo-hazelnut-dragees-45` | Mozimo Hazelnut Dragees 45% | Mozimo Hand Crafted Chocolates | `200gm` | [{"name":"Title","value":"200gm"}] | 900.0 | — | -1 | -1 | yes | 1 | `dragees`, `best-sellers` |
|
||||
| 33 | `mozimo-hazelnut-dragees-60` | Mozimo Hazelnut Dragees 60% | Mozimo Hand Crafted Chocolates | `200gm` | [{"name":"Title","value":"200gm"}] | 900.0 | — | -2 | -2 | yes | 1 | `dragees` |
|
||||
| 34 | `mozimo-hazelnut-praline-bark` | Mozimo Hazelnut Praline Bark | Mozimo Hand Crafted Chocolates | `200gm` | [{"name":"Title","value":"200gm"}] | 1200.0 | — | -2 | -2 | yes | 1 | `pralines-bark` |
|
||||
| 35 | `mozimo-hazelnuts-spread` | Mozimo Single Origin 45% Hazelnut Spread | Mozimo Hand Crafted Chocolates | `200gm` | [{"name":"Title","value":"200gm"}] | 550.0 | — | -5 | -5 | yes | 1 | `spreads`, `best-sellers` |
|
||||
| 36 | `mozimo-honey-comb` | Mozimo Honey Comb | Mozimo Hand Crafted Chocolates | `100gm` | [{"name":"Title","value":"100gm"}] | 400.0 | — | -4 | -4 | yes | 1 | `mozimo-delights` |
|
||||
| 37 | `mozimo-hot-chocolate-premix` | Mozimo Hot Chocolate Premix | Mozimo Hand Crafted Chocolates | `250gm` | [{"name":"Title","value":"250gm"}] | 825.0 | — | 0 | 0 | yes | 1 | `best-sellers`, `mozimo-delights` |
|
||||
| 38 | `mozimo-matcha-bark` | Mozimo Matcha Bark Box 200gm | Mozimo Hand Crafted Chocolates | `Default Title` | [{"name":"Title","value":"Default Title"}] | 1600.0 | — | 0 | 0 | yes | 1 | `frontpage`, `best-sellers` |
|
||||
| 39 | `mozimo-matcha-collection-box` | Mozimo Matcha Collection Box | Mozimo Hand Crafted Chocolates | `Default Title` | [{"name":"Title","value":"Default Title"}] | 3500.0 | — | 0 | 0 | yes | 1 | `frontpage`, `best-sellers`, `new-arrivals`, `gift-collection` |
|
||||
| 40 | `mozimo-milk-45-tuilles` | Mozimo Milk 45% Tuilles | Mozimo Hand Crafted Chocolates | `100gm` | [{"name":"Title","value":"100gm"}] | 500.0 | — | -2 | -2 | yes | 1 | `tuilles` |
|
||||
| 41 | `mozimo-milk-chocolate-bar-45` | Mozimo Milk Chocolate Bar 45% | Mozimo Hand Crafted Chocolates | `60gm` | [{"name":"Title","value":"60gm"}] | 265.0 | — | -20 | -20 | yes | 1 | `bars` |
|
||||
| 42 | `mozimo-nutty-bar` | Mozimo Nutty Bar | Mozimo Hand Crafted Chocolates | `1` | [{"name":"Title","value":"1"}] | 500.0 | — | -3 | -3 | yes | 1 | `bars`, `mozimo-delights` |
|
||||
| 43 | `mozimo-orangettes` | Mozimo Orangettes | Mozimo Hand Crafted Chocolates | `100gm` | [{"name":"Title","value":"100gm"}] | 500.0 | — | -7 | -7 | yes | 1 | `best-sellers`, `rocks-orangettes` |
|
||||
| 44 | `mozimo-pecan-bark` | Mozimo Pecan Bark | Mozimo Hand Crafted Chocolates | `200gm` | [{"name":"Title","value":"200gm"}] | 1200.0 | — | -2 | -2 | yes | 1 | `bark` |
|
||||
| 45 | `mozimo-pistachio-dragees` | Mozimo Pistachio Dragees | Mozimo Hand Crafted Chocolates | `200gm` | [{"name":"Title","value":"200gm"}] | 900.0 | — | 0 | 0 | yes | 1 | — |
|
||||
| 46 | `mozimo-praline-box-of-25` | Mozimo Praline (Box of 25) | Mozimo Hand Crafted Chocolates | `Default Title` | [{"name":"Title","value":"Default Title"}] | 2700.0 | — | 0 | 0 | yes | 1 | `diwali` |
|
||||
| 47 | `mozimo-praline-box-of-36` | Mozimo Praline (Box of 36) | Mozimo Hand Crafted Chocolates | `Default Title` | [{"name":"Title","value":"Default Title"}] | 3700.0 | — | 0 | 0 | yes | 1 | `best-sellers`, `diwali` |
|
||||
| 48 | `mozimo-praline-panetela` | Mozimo Praline Panetela | Mozimo Hand Crafted Chocolates | `12pc` | [{"name":"Title","value":"12pc"}] | 1300.0 | — | -1 | -1 | yes | 1 | `pralines` |
|
||||
| 49 | `mozimo-praline-panetela-1` | Mozimo Praline Panetela (Box of 12) | Mozimo Hand Crafted Chocolates | `Default Title` | [{"name":"Title","value":"Default Title"}] | 1300.0 | — | 0 | 0 | yes | 1 | `best-sellers`, `diwali` |
|
||||
| 50 | `mozimo-raspberry-bark` | Mozimo Raspberry Bark | Mozimo Hand Crafted Chocolates | `100gm` | [{"name":"Title","value":"100gm"}] | 500.0 | — | -2 | -2 | yes | 1 | `bark` |
|
||||
| 51 | `mozimo-rice-crispy-bark` | Mozimo Rice crispy Bark | Mozimo Hand Crafted Chocolates | `200gm` | [{"name":"Title","value":"200gm"}] | 1200.0 | — | -1 | -1 | yes | 1 | `bark` |
|
||||
| 52 | `mozimo-salted-butter-crunch-bark-45` | Mozimo Salted Butter Crunch Bark 45% | Mozimo Hand Crafted Chocolates | `100gm` | [{"name":"Title","value":"100gm"}] | 500.0 | — | -2 | -2 | yes | 1 | `bark` |
|
||||
| 53 | `mozimo-sugar-free-dark-72-chocolate-bar` | Mozimo Sugar Free Dark 72% Chocolate Bar | Mozimo Hand Crafted Chocolates | `60gm` | [{"name":"Title","value":"60gm"}] | 330.0 | — | -6 | -6 | yes | 1 | `bars`, `best-sellers`, `sugar-free-chocolate-bar` |
|
||||
| 54 | `mozimo-sugar-free-milk-45-chocolate-bar` | Mozimo Sugar Free Milk 45% Chocolate Bar | Mozimo Hand Crafted Chocolates | `Default Title` | [{"name":"Title","value":"Default Title"}] | 330.0 | — | -1 | -1 | yes | 1 | `bars` |
|
||||
| 55 | `mozimo-white-chocolate-tuilles` | Mozimo White Chocolate Tuilles | Mozimo Hand Crafted Chocolates | `100gm` | [{"name":"Title","value":"100gm"}] | 500.0 | — | -1 | -1 | yes | 1 | `tuilles` |
|
||||
| 56 | `mozimo-whole-hazelnuts-bark-45` | Mozimo Whole Hazelnuts Bark 45% | Mozimo Hand Crafted Chocolates | `100gm` | [{"name":"Title","value":"100gm"}] | 500.0 | — | -6 | -6 | yes | 1 | `bark` |
|
||||
| 57 | `mozimo-whole-hazelnuts-bark-60` | Mozimo Whole Hazelnuts Bark 60% | Mozimo Hand Crafted Chocolates | `100gm` | [{"name":"Title","value":"100gm"}] | 500.0 | — | -1 | -1 | yes | 1 | `bark` |
|
||||
| 58 | `pistachio-dragees` | Pistachio Dragees | My Store | `Default Title` | [{"name":"Title","value":"Default Title"}] | 900.0 | — | -1 | -1 | yes | 1 | `dragees` |
|
||||
| 59 | `pistachio-spread-60` | Pistachio Spread 60% | Mozimo Hand Crafted Chocolates | `200gm` | [{"name":"Title","value":"200gm"}] | 825.0 | — | -2 | -2 | yes | 1 | `spreads`, `best-sellers` |
|
||||
| 60 | `single-origin-coco-nibs` | Single Origin Coco Nibs | Mozimo Hand Crafted Chocolates | `150gm` | [{"name":"Title","value":"150gm"}] | 440.0 | — | -2 | -2 | yes | 1 | `mozimo-delights` |
|
||||
| 61 | `spreads-box` | Spreads Box | Mozimo Hand Crafted Chocolates | `Default Title` | [{"name":"Title","value":"Default Title"}] | 1650.0 | — | 0 | 0 | yes | 1 | `diwali` |
|
||||
|
||||
---
|
||||
|
||||
## 3. Variant structure — the key question
|
||||
|
||||
### Direct answer
|
||||
|
||||
> **No product in this store has more than one variant.** All **61 of 61**
|
||||
> products have exactly **1 variant**; **0** products have 2 or more.
|
||||
> `variantsCount.count == 1` for every product, and total variant rows across the
|
||||
> catalogue = **61**. Every product without exception exposes a single option
|
||||
> named `Title` with exactly one selected value.
|
||||
|
||||
### How multi-size products are modelled: **separate products, not variants**
|
||||
|
||||
The option is literally `Title` (Shopify's default single-variant option), and
|
||||
`selectedOptions` always has exactly one entry:
|
||||
|
||||
| `selectedOptions` value | Products |
|
||||
| -------------------------------------------- | -------: |
|
||||
| `[{"name":"Title","value":"Default Title"}]` | 22 |
|
||||
| `[{"name":"Title","value":"100gm"}]` | 17 |
|
||||
| `[{"name":"Title","value":"200gm"}]` | 11 |
|
||||
| `[{"name":"Title","value":"60gm"}]` | 5 |
|
||||
| `[{"name":"Title","value":"1"}]` | 3 |
|
||||
| `[{"name":"Title","value":"250gm"}]` | 1 |
|
||||
| `[{"name":"Title","value":"150gm"}]` | 1 |
|
||||
| `[{"name":"Title","value":"12pc"}]` | 1 |
|
||||
|
||||
No product exposes an option named `Size`, `Weight` or `Colour`, and no product
|
||||
offers two selectable values. Where a weight appears anywhere, it lives in the
|
||||
_variant title_ of a single-variant product (`60gm`, `100gm`, `200gm`, `250gm`,
|
||||
`150gm`, `12pc`, or a bare `1`), and the `Title` option carries it as its sole
|
||||
value.
|
||||
|
||||
Size-family products are **separate products with separate handles**. Actual
|
||||
titles, variant titles and prices, as evidence:
|
||||
|
||||
| handle | product title | variant title | price ₹ | collections |
|
||||
| ------------------------------- | ------------------------------- | --------------- | ------- | --------------------------------------------------------------------- |
|
||||
| `mozimo-carnival-celebration-s` | Mozimo Carnival Celebration (S) | `Default Title` | 2400.0 | gift-collection, diwali, hampers-boxes |
|
||||
| `mozimo-carnival-celebration-m` | Mozimo Carnival Celebration (M) | `Default Title` | 3000.0 | best-sellers, gift-collection, diwali, hampers-boxes |
|
||||
| `mozimo-carnival-celebration-l` | Mozimo Carnival Celebration (L) | `Default Title` | 3900.0 | gift-collection, diwali, hampers-boxes |
|
||||
| `mozimo-celestial-s` | Mozimo Celestial (S) | `Default Title` | 4500.0 | gift-collection, diwali, hampers-boxes |
|
||||
| `mozimo-celestial-l` | Mozimo Celestial Hamper | `Default Title` | 8500.0 | frontpage, best-sellers, new-arrivals, gift-collection, hampers-boxes |
|
||||
|
||||
So Carnival S/M/L and Celestial S/L are **5 distinct products**, each with one
|
||||
`Default Title` variant. The size marker is inconsistent in the titles: Celestial
|
||||
L's title is `Mozimo Celestial Hamper` with no `(L)`, while its handle carries
|
||||
`-l`. Handles ending `-s`/`-m`/`-l` number exactly five:
|
||||
`mozimo-carnival-celebration-{s,m,l}`, `mozimo-celestial-{s,l}`.
|
||||
|
||||
Other size/dimension families are likewise separate products:
|
||||
`mozimo-chocolate-muesli-200gm` vs `mozimo-chocolate-muesli-400gm`,
|
||||
`mozimo-praline-box-of-25` vs `mozimo-praline-box-of-36`,
|
||||
`1-kg-bark-box` vs `200-gas-bark-box`, and
|
||||
`mozimo-dark-chocolate-bar-60` / `-72` / `-85` plus `mozimo-milk-chocolate-bar-45`.
|
||||
|
||||
**Conclusion for both cases: separate products only — never variants. There is no
|
||||
product in this store that serves several sizes as selectable options.**
|
||||
|
||||
### Full variant list — all 61 variants
|
||||
|
||||
| handle | variant GID | variant title | selectedOptions | price | compareAtPrice | availableForSale | quantityAvailable |
|
||||
| ----------------------------------------- | --------------------------------------------- | ------------- | ------------------------------------------ | ------- | -------------- | ---------------- | ----------------- |
|
||||
| `1-kg-bark-box` | `gid://shopify/ProductVariant/49079272046838` | Default Title | [{"name":"Title","value":"Default Title"}] | ₹5700.0 | null | true | 0 |
|
||||
| `200-gas-bark-box` | `gid://shopify/ProductVariant/49079244456182` | Default Title | [{"name":"Title","value":"Default Title"}] | ₹1200.0 | null | true | 0 |
|
||||
| `4-pralines-box` | `gid://shopify/ProductVariant/47806947557622` | Default Title | [{"name":"Title","value":"Default Title"}] | ₹440.0 | null | true | -2 |
|
||||
| `9-pralines-box` | `gid://shopify/ProductVariant/47806947590390` | 1 | [{"name":"Title","value":"1"}] | ₹990.0 | null | true | -3 |
|
||||
| `bars-box` | `gid://shopify/ProductVariant/49078921101558` | Default Title | [{"name":"Title","value":"Default Title"}] | ₹1050.0 | null | true | 0 |
|
||||
| `cocoa-bean-shell` | `gid://shopify/ProductVariant/47806947459318` | 100gm | [{"name":"Title","value":"100gm"}] | ₹220.0 | null | true | 0 |
|
||||
| `kunafa-chocolate-bar` | `gid://shopify/ProductVariant/49079263101174` | Default Title | [{"name":"Title","value":"Default Title"}] | ₹1600.0 | null | true | -6 |
|
||||
| `matcha-hazelnut-dragees-100gm` | `gid://shopify/ProductVariant/50112861831414` | Default Title | [{"name":"Title","value":"Default Title"}] | ₹550.0 | null | true | -1 |
|
||||
| `matcha-strawberry-praline` | `gid://shopify/ProductVariant/49956310417654` | Default Title | [{"name":"Title","value":"Default Title"}] | ₹440.0 | null | true | -1 |
|
||||
| `mozimo-almond-bark-45` | `gid://shopify/ProductVariant/47806946607350` | 100gm | [{"name":"Title","value":"100gm"}] | ₹500.0 | null | true | -2 |
|
||||
| `mozimo-almond-bark-60` | `gid://shopify/ProductVariant/47806946574582` | 100gm | [{"name":"Title","value":"100gm"}] | ₹500.0 | null | true | 0 |
|
||||
| `mozimo-almond-dragees-45` | `gid://shopify/ProductVariant/47806946640118` | 200gm | [{"name":"Title","value":"200gm"}] | ₹900.0 | null | true | -1 |
|
||||
| `mozimo-almond-dragees-60` | `gid://shopify/ProductVariant/47806946869494` | 200gm | [{"name":"Title","value":"200gm"}] | ₹900.0 | null | true | -2 |
|
||||
| `mozimo-almond-praline-bark` | `gid://shopify/ProductVariant/47806947197174` | 200gm | [{"name":"Title","value":"200gm"}] | ₹1200.0 | null | true | -2 |
|
||||
| `mozimo-almond-slivers-rocks` | `gid://shopify/ProductVariant/47806946672886` | 100gm | [{"name":"Title","value":"100gm"}] | ₹500.0 | null | true | -3 |
|
||||
| `mozimo-assorted-bark-box` | `gid://shopify/ProductVariant/47806947623158` | 1 | [{"name":"Title","value":"1"}] | ₹1200.0 | null | true | -1 |
|
||||
| `mozimo-carnival-celebration-l` | `gid://shopify/ProductVariant/49079154082038` | Default Title | [{"name":"Title","value":"Default Title"}] | ₹3900.0 | null | true | 0 |
|
||||
| `mozimo-carnival-celebration-m` | `gid://shopify/ProductVariant/49079147725046` | Default Title | [{"name":"Title","value":"Default Title"}] | ₹3000.0 | null | true | 0 |
|
||||
| `mozimo-carnival-celebration-s` | `gid://shopify/ProductVariant/49079145595126` | Default Title | [{"name":"Title","value":"Default Title"}] | ₹2400.0 | null | true | -2 |
|
||||
| `mozimo-celestial-l` | `gid://shopify/ProductVariant/49079213293814` | Default Title | [{"name":"Title","value":"Default Title"}] | ₹8500.0 | null | true | 0 |
|
||||
| `mozimo-celestial-s` | `gid://shopify/ProductVariant/49079192748278` | Default Title | [{"name":"Title","value":"Default Title"}] | ₹4500.0 | null | true | -2 |
|
||||
| `mozimo-chocolate-muesli-200gm` | `gid://shopify/ProductVariant/50137431736566` | Default Title | [{"name":"Title","value":"Default Title"}] | ₹300.0 | null | true | -2 |
|
||||
| `mozimo-chocolate-muesli-400gm` | `gid://shopify/ProductVariant/50137463685366` | Default Title | [{"name":"Title","value":"Default Title"}] | ₹550.0 | null | true | -3 |
|
||||
| `mozimo-crunchy-granola-bark-45` | `gid://shopify/ProductVariant/47806946541814` | 100gm | [{"name":"Title","value":"100gm"}] | ₹500.0 | null | true | -1 |
|
||||
| `mozimo-dark-chocolate-bar-60` | `gid://shopify/ProductVariant/47806947295478` | 60gm | [{"name":"Title","value":"60gm"}] | ₹265.0 | null | true | -5 |
|
||||
| `mozimo-dark-chocolate-bar-72` | `gid://shopify/ProductVariant/47806947098870` | 60gm | [{"name":"Title","value":"60gm"}] | ₹265.0 | null | true | -1 |
|
||||
| `mozimo-dark-chocolate-bar-85` | `gid://shopify/ProductVariant/47806947328246` | 60gm | [{"name":"Title","value":"60gm"}] | ₹265.0 | null | true | -1 |
|
||||
| `mozimo-fruits-and-nuts-bark-60` | `gid://shopify/ProductVariant/47806947852534` | 100gm | [{"name":"Title","value":"100gm"}] | ₹500.0 | null | true | -1 |
|
||||
| `mozimo-fruits-nuts-45` | `gid://shopify/ProductVariant/47806946279670` | 100gm | [{"name":"Title","value":"100gm"}] | ₹500.0 | null | true | -2 |
|
||||
| `mozimo-gems-bark` | `gid://shopify/ProductVariant/47806946509046` | 100gm | [{"name":"Title","value":"100gm"}] | ₹500.0 | null | true | -1 |
|
||||
| `mozimo-hazelnut-bark-sugar-free-45` | `gid://shopify/ProductVariant/47806947721462` | 100gm | [{"name":"Title","value":"100gm"}] | ₹550.0 | null | true | -8 |
|
||||
| `mozimo-hazelnut-dragees-45` | `gid://shopify/ProductVariant/47806946476278` | 200gm | [{"name":"Title","value":"200gm"}] | ₹900.0 | null | true | -1 |
|
||||
| `mozimo-hazelnut-dragees-60` | `gid://shopify/ProductVariant/47806946312438` | 200gm | [{"name":"Title","value":"200gm"}] | ₹900.0 | null | true | -2 |
|
||||
| `mozimo-hazelnut-praline-bark` | `gid://shopify/ProductVariant/47806947000566` | 200gm | [{"name":"Title","value":"200gm"}] | ₹1200.0 | null | true | -2 |
|
||||
| `mozimo-hazelnuts-spread` | `gid://shopify/ProductVariant/47806946771190` | 200gm | [{"name":"Title","value":"200gm"}] | ₹550.0 | null | true | -5 |
|
||||
| `mozimo-honey-comb` | `gid://shopify/ProductVariant/47806947164406` | 100gm | [{"name":"Title","value":"100gm"}] | ₹400.0 | null | true | -4 |
|
||||
| `mozimo-hot-chocolate-premix` | `gid://shopify/ProductVariant/47806946803958` | 250gm | [{"name":"Title","value":"250gm"}] | ₹825.0 | null | true | 0 |
|
||||
| `mozimo-matcha-bark` | `gid://shopify/ProductVariant/50112483590390` | Default Title | [{"name":"Title","value":"Default Title"}] | ₹1600.0 | null | true | 0 |
|
||||
| `mozimo-matcha-collection-box` | `gid://shopify/ProductVariant/49947063976182` | Default Title | [{"name":"Title","value":"Default Title"}] | ₹3500.0 | null | true | 0 |
|
||||
| `mozimo-milk-45-tuilles` | `gid://shopify/ProductVariant/47806946935030` | 100gm | [{"name":"Title","value":"100gm"}] | ₹500.0 | null | true | -2 |
|
||||
| `mozimo-milk-chocolate-bar-45` | `gid://shopify/ProductVariant/47806947361014` | 60gm | [{"name":"Title","value":"60gm"}] | ₹265.0 | null | true | -20 |
|
||||
| `mozimo-nutty-bar` | `gid://shopify/ProductVariant/47806946738422` | 1 | [{"name":"Title","value":"1"}] | ₹500.0 | null | true | -3 |
|
||||
| `mozimo-orangettes` | `gid://shopify/ProductVariant/47806947393782` | 100gm | [{"name":"Title","value":"100gm"}] | ₹500.0 | null | true | -7 |
|
||||
| `mozimo-pecan-bark` | `gid://shopify/ProductVariant/47806947033334` | 200gm | [{"name":"Title","value":"200gm"}] | ₹1200.0 | null | true | -2 |
|
||||
| `mozimo-pistachio-dragees` | `gid://shopify/ProductVariant/47806946345206` | 200gm | [{"name":"Title","value":"200gm"}] | ₹900.0 | null | true | 0 |
|
||||
| `mozimo-praline-box-of-25` | `gid://shopify/ProductVariant/49079282696438` | Default Title | [{"name":"Title","value":"Default Title"}] | ₹2700.0 | null | true | 0 |
|
||||
| `mozimo-praline-box-of-36` | `gid://shopify/ProductVariant/49079289741558` | Default Title | [{"name":"Title","value":"Default Title"}] | ₹3700.0 | null | true | 0 |
|
||||
| `mozimo-praline-panetela` | `gid://shopify/ProductVariant/47806947131638` | 12pc | [{"name":"Title","value":"12pc"}] | ₹1300.0 | null | true | -1 |
|
||||
| `mozimo-praline-panetela-1` | `gid://shopify/ProductVariant/49079278239990` | Default Title | [{"name":"Title","value":"Default Title"}] | ₹1300.0 | null | true | 0 |
|
||||
| `mozimo-raspberry-bark` | `gid://shopify/ProductVariant/47806946705654` | 100gm | [{"name":"Title","value":"100gm"}] | ₹500.0 | null | true | -2 |
|
||||
| `mozimo-rice-crispy-bark` | `gid://shopify/ProductVariant/47806947066102` | 200gm | [{"name":"Title","value":"200gm"}] | ₹1200.0 | null | true | -1 |
|
||||
| `mozimo-salted-butter-crunch-bark-45` | `gid://shopify/ProductVariant/47806947786998` | 100gm | [{"name":"Title","value":"100gm"}] | ₹500.0 | null | true | -2 |
|
||||
| `mozimo-sugar-free-dark-72-chocolate-bar` | `gid://shopify/ProductVariant/47806947655926` | 60gm | [{"name":"Title","value":"60gm"}] | ₹330.0 | null | true | -6 |
|
||||
| `mozimo-sugar-free-milk-45-chocolate-bar` | `gid://shopify/ProductVariant/49080727372022` | Default Title | [{"name":"Title","value":"Default Title"}] | ₹330.0 | null | true | -1 |
|
||||
| `mozimo-white-chocolate-tuilles` | `gid://shopify/ProductVariant/47806946902262` | 100gm | [{"name":"Title","value":"100gm"}] | ₹500.0 | null | true | -1 |
|
||||
| `mozimo-whole-hazelnuts-bark-45` | `gid://shopify/ProductVariant/47806946410742` | 100gm | [{"name":"Title","value":"100gm"}] | ₹500.0 | null | true | -6 |
|
||||
| `mozimo-whole-hazelnuts-bark-60` | `gid://shopify/ProductVariant/47806946377974` | 100gm | [{"name":"Title","value":"100gm"}] | ₹500.0 | null | true | -1 |
|
||||
| `pistachio-dragees` | `gid://shopify/ProductVariant/47801725092086` | Default Title | [{"name":"Title","value":"Default Title"}] | ₹900.0 | null | true | -1 |
|
||||
| `pistachio-spread-60` | `gid://shopify/ProductVariant/47806947754230` | 200gm | [{"name":"Title","value":"200gm"}] | ₹825.0 | null | true | -2 |
|
||||
| `single-origin-coco-nibs` | `gid://shopify/ProductVariant/47806947426550` | 150gm | [{"name":"Title","value":"150gm"}] | ₹440.0 | null | true | -2 |
|
||||
| `spreads-box` | `gid://shopify/ProductVariant/49079091003638` | Default Title | [{"name":"Title","value":"Default Title"}] | ₹1650.0 | null | true | 0 |
|
||||
|
||||
`sku` is `null`/`""` and `barcode` unset for every row above; `weight` is set
|
||||
(GRAMS) for every row; `taxable` and `requiresShipping` are `true` for every row.
|
||||
|
||||
---
|
||||
|
||||
## 4. Collections
|
||||
|
||||
**16 collections**, returned complete by `collections(first: 50)` with no next
|
||||
page. Every collection's product list was read separately and none was truncated
|
||||
(`hasNextPage: false` on a `first: 250` products connection).
|
||||
|
||||
| handle | title | products | image | description |
|
||||
| -------------------------- | ------------------------ | -------: | ----- | ------------------------------------------------------------------------------------------ |
|
||||
| `bark` | Barks | 15 | — | — |
|
||||
| `bars` | Bars | 7 | — | Chocolate Bars |
|
||||
| `best-sellers` | Best Sellers | 17 | yes | — |
|
||||
| `diwali` | Diwali | 12 | — | Diwali Gifting Collection |
|
||||
| `dragees` | Dragees | 6 | — | Dragees |
|
||||
| `frontpage` | Home page | 5 | — | — |
|
||||
| `gift-collection` | Gift Collection | 10 | yes | Discover the art of thoughtful gifting with Mozimo’s handcrafted chocolate gifting collect |
|
||||
| `hampers-boxes` | Hampers & Boxes | 6 | — | — |
|
||||
| `mozimo-delights` | Mozimo Delights | 5 | — | — |
|
||||
| `new-arrivals` | New Arrivals | 4 | yes | — |
|
||||
| `pralines` | Pralines | 4 | — | — |
|
||||
| `pralines-bark` | Pralines Bark | 2 | — | — |
|
||||
| `rocks-orangettes` | Rocks / Orangettes | 2 | — | — |
|
||||
| `spreads` | Spreads | 2 | — | Spreads |
|
||||
| `sugar-free-chocolate-bar` | Sugar Free Chocolate Bar | 1 | — | — |
|
||||
| `tuilles` | Tuilles | 2 | — | — |
|
||||
|
||||
### Membership of the main collections
|
||||
|
||||
**`bark` (Barks) — 15**
|
||||
`mozimo-almond-bark-45`, `mozimo-almond-bark-60`, `mozimo-whole-hazelnuts-bark-45`,
|
||||
`mozimo-whole-hazelnuts-bark-60`, `mozimo-fruits-nuts-45`,
|
||||
`mozimo-fruits-and-nuts-bark-60`, `mozimo-gems-bark`, `mozimo-rice-crispy-bark`,
|
||||
`mozimo-raspberry-bark`, `mozimo-pecan-bark`, `mozimo-crunchy-granola-bark-45`,
|
||||
`mozimo-salted-butter-crunch-bark-45`, `mozimo-hazelnut-bark-sugar-free-45`,
|
||||
`mozimo-assorted-bark-box`, `mozimo-almond-praline-bark`
|
||||
|
||||
**`bars` (Bars) — 7**
|
||||
`mozimo-dark-chocolate-bar-85`, `mozimo-dark-chocolate-bar-72`,
|
||||
`mozimo-dark-chocolate-bar-60`, `mozimo-milk-chocolate-bar-45`,
|
||||
`mozimo-sugar-free-dark-72-chocolate-bar`,
|
||||
`mozimo-sugar-free-milk-45-chocolate-bar`, `mozimo-nutty-bar`
|
||||
|
||||
**`best-sellers` (Best Sellers) — 17**
|
||||
`mozimo-hot-chocolate-premix`, `200-gas-bark-box`, `kunafa-chocolate-bar`,
|
||||
`mozimo-celestial-l`, `mozimo-carnival-celebration-m`,
|
||||
`mozimo-dark-chocolate-bar-60`, `mozimo-sugar-free-dark-72-chocolate-bar`,
|
||||
`mozimo-hazelnut-dragees-45`, `mozimo-orangettes`, `mozimo-praline-box-of-36`,
|
||||
`mozimo-praline-panetela-1`, `mozimo-hazelnuts-spread`, `pistachio-spread-60`,
|
||||
`mozimo-matcha-collection-box`, `mozimo-matcha-bark`,
|
||||
`matcha-hazelnut-dragees-100gm`, `mozimo-chocolate-muesli-400gm`
|
||||
|
||||
**`diwali` (Diwali) — 12**
|
||||
`bars-box`, `spreads-box`, `mozimo-carnival-celebration-s`,
|
||||
`mozimo-carnival-celebration-m`, `mozimo-carnival-celebration-l`,
|
||||
`mozimo-celestial-s`, `200-gas-bark-box`, `kunafa-chocolate-bar`,
|
||||
`1-kg-bark-box`, `mozimo-praline-panetela-1`, `mozimo-praline-box-of-25`,
|
||||
`mozimo-praline-box-of-36`
|
||||
|
||||
**`gift-collection` (Gift Collection) — 10**
|
||||
`mozimo-celestial-l`, `1-kg-bark-box`, `mozimo-celestial-s`,
|
||||
`mozimo-carnival-celebration-l`, `mozimo-matcha-collection-box`,
|
||||
`mozimo-carnival-celebration-m`, `mozimo-carnival-celebration-s`,
|
||||
`200-gas-bark-box`, `bars-box`, `9-pralines-box`
|
||||
|
||||
**`hampers-boxes` (Hampers & Boxes) — 6**
|
||||
`mozimo-celestial-s`, `mozimo-carnival-celebration-s`, `1-kg-bark-box`,
|
||||
`mozimo-celestial-l`, `mozimo-carnival-celebration-l`,
|
||||
`mozimo-carnival-celebration-m`
|
||||
|
||||
**`dragees` (Dragees) — 6**
|
||||
`mozimo-almond-dragees-60`, `mozimo-almond-dragees-45`,
|
||||
`mozimo-hazelnut-dragees-60`, `mozimo-hazelnut-dragees-45`, `pistachio-dragees`,
|
||||
`matcha-hazelnut-dragees-100gm`
|
||||
|
||||
**`pralines` (Pralines) — 4**
|
||||
`9-pralines-box`, `matcha-strawberry-praline`, `4-pralines-box`,
|
||||
`mozimo-praline-panetela`
|
||||
|
||||
**`frontpage` (Home page) — 5**
|
||||
`mozimo-chocolate-muesli-400gm`, `mozimo-chocolate-muesli-200gm`,
|
||||
`mozimo-matcha-bark`, `mozimo-matcha-collection-box`, `mozimo-celestial-l`
|
||||
|
||||
**`mozimo-delights` (Mozimo Delights) — 5**
|
||||
`mozimo-honey-comb`, `single-origin-coco-nibs`, `mozimo-nutty-bar`,
|
||||
`cocoa-bean-shell`, `mozimo-hot-chocolate-premix`
|
||||
|
||||
**`new-arrivals` (New Arrivals) — 4**
|
||||
`mozimo-matcha-collection-box`, `mozimo-chocolate-muesli-200gm`,
|
||||
`mozimo-chocolate-muesli-400gm`, `mozimo-celestial-l`
|
||||
|
||||
**`spreads` (Spreads) — 2**
|
||||
`mozimo-hazelnuts-spread`, `pistachio-spread-60`
|
||||
|
||||
**`tuilles` (Tuilles) — 2**
|
||||
`mozimo-milk-45-tuilles`, `mozimo-white-chocolate-tuilles`
|
||||
|
||||
**`rocks-orangettes` (Rocks / Orangettes) — 2**
|
||||
`mozimo-orangettes`, `mozimo-almond-slivers-rocks`
|
||||
|
||||
**`pralines-bark` (Pralines Bark) — 2**
|
||||
`mozimo-almond-praline-bark`, `mozimo-hazelnut-praline-bark`
|
||||
|
||||
**`sugar-free-chocolate-bar` (Sugar Free Chocolate Bar) — 1**
|
||||
`mozimo-sugar-free-dark-72-chocolate-bar`
|
||||
|
||||
**Products in no collection at all: `mozimo-pistachio-dragees`** (published and
|
||||
`availableForSale: true`, yet in zero collections).
|
||||
`mozimo-hazelnut-bark-sugar-free-45` sits in `bark` only and is **not** in
|
||||
`sugar-free-chocolate-bar`.
|
||||
|
||||
### Divergence from `src/data/catalog.ts`
|
||||
|
||||
Hardcoded collection URLs found in `catalog.ts` (`collections`, `signatures`,
|
||||
`giftingTabs`, `experienceTiles`) versus live:
|
||||
|
||||
| hardcoded `url:` | live status | live product count |
|
||||
| ------------------------------ | ------------------------------------------------------- | --------------------------------------------------------------------------------------------------- |
|
||||
| `/collections/bars` | exists | 7 |
|
||||
| `/collections/bark` | exists | 15 |
|
||||
| `/collections/pralines` | exists | 4 |
|
||||
| `/collections/spreads` | exists | 2 |
|
||||
| `/collections/dragees` | exists | 6 |
|
||||
| `/collections/tuilles` | exists | 2 |
|
||||
| `/collections/gift-collection` | exists | 10 |
|
||||
| `/collections/best-sellers` | exists | 17 |
|
||||
| `/collections/diwali` | exists | 12 |
|
||||
| `/collections/all` | **not returned by `collection(handle:"all")`** (`null`) | — (Shopify's built-in "all" pseudo-collection is not exposed via this handle on the Storefront API) |
|
||||
|
||||
Named-collection divergences:
|
||||
|
||||
| `catalog.ts` collection name | hardcoded `url` | live collection with that name | Divergence |
|
||||
| ---------------------------- | ------------------------------ | ----------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| Bars | `/collections/bars` | `bars` — "Bars" | name matches; live count 7 |
|
||||
| Barks | `/collections/bark` | `bark` — "Barks" | name matches; live count 15 |
|
||||
| Pralines | `/collections/pralines` | `pralines` — "Pralines" | name matches; live count 4 |
|
||||
| Spreads | `/collections/spreads` | `spreads` — "Spreads" | name matches; live count 2 |
|
||||
| Dragees | `/collections/dragees` | `dragees` — "Dragees" | name matches; live count 6 |
|
||||
| Tuilles & Rocks | `/collections/tuilles` | `tuilles` — "Tuilles" **and** `rocks-orangettes` — "Rocks / Orangettes" | **merged in code, split in Shopify**: `mozimo-orangettes` and `mozimo-almond-slivers-rocks` live in `rocks-orangettes`, not `tuilles` |
|
||||
| Gifting | `/collections/gift-collection` | `gift-collection` — "Gift Collection" | name matches; live count 10 |
|
||||
|
||||
`signatures` in `catalog.ts` points only at existing handles (`gift-collection`,
|
||||
`pralines`, `bars`, `all`) but its entries "The Signature Box", "The Praline
|
||||
Collection", "The Dark Collection", "The Festive Collection", "The Chocolate Bar"
|
||||
and "The Mozimo Gifts" have **no corresponding collection objects in the store** —
|
||||
they are editorial constructs, not Shopify collections. Seven Shopify
|
||||
collections have a live counterpart referenced from code: bars, bark, pralines,
|
||||
spreads, dragees, tuilles, plus gift-collection for the Gifting entry.
|
||||
|
||||
Collections that exist in Shopify but are **absent from `catalog.ts`** entirely
|
||||
(no hardcoded `url` references them):
|
||||
|
||||
`best-sellers` (17), `diwali` (12), `hampers-boxes` (6), `frontpage` (5),
|
||||
`mozimo-delights` (5), `new-arrivals` (4), `pralines-bark` (2),
|
||||
`rocks-orangettes` (2, but its products are miscategorised under `tuilles` in
|
||||
code), `sugar-free-chocolate-bar` (1). Only `best-sellers` and `diwali` appear in
|
||||
code at all, and only as CTA URLs inside `giftingTabs`, not as data.
|
||||
|
||||
`giftingTabs` in `catalog.ts` (7 tabs; 4 with product lists):
|
||||
|
||||
| tab id | name | note | hardcoded product handles | all handles live? |
|
||||
| -------------- | ---------------- | ---------------- | ---------------------------------------------------------------------------------------- | ----------------- |
|
||||
| `signature` | Signature | From ₹265 | `mozimo-dark-chocolate-bar-85`, `9-pralines-box`, `bars-box` | yes |
|
||||
| `best-sellers` | Best Sellers | From ₹825 | `pistachio-spread-60`, `mozimo-almond-dragees-60`, `kunafa-chocolate-bar`, `spreads-box` | yes |
|
||||
| `exquisite` | Exquisite | From ₹2,400 | `mozimo-carnival-celebration-s`, `mozimo-carnival-celebration-m` | yes |
|
||||
| `premium` | Premium | From ₹3,900 | `mozimo-carnival-celebration-l`, `mozimo-celestial-s` | yes |
|
||||
| `seasonal` | Seasonal | Limited editions | none (`mode: "seasonal"`) | n/a |
|
||||
| `corporate` | Corporate & Bulk | At scale | none (`mode: "enquiry"`) | n/a |
|
||||
| `bespoke` | Bespoke Gifting | Made to order | none (`mode: "enquiry"`) | n/a |
|
||||
|
||||
Tab CTAs point to `/collections/gift-collection` (all but `best-sellers`, which
|
||||
points to `/collections/best-sellers`). Live `best-sellers` contains **17**
|
||||
products; the code's tab lists only 4 of them.
|
||||
|
||||
Hardcoded product handles in `catalog.ts` (`bestsellers`, `gifting`, `pantry`,
|
||||
`giftingTabs`, `seasonal`) versus live — **all 15 resolve**:
|
||||
|
||||
| handle | live title | live price ₹ | in code as |
|
||||
| ------------------------------- | ------------------------------- | ------------ | ----------------------------------- |
|
||||
| `mozimo-dark-chocolate-bar-85` | Mozimo Dark Chocolate Bar 85% | 265.0 | bestsellers, signature tab |
|
||||
| `9-pralines-box` | 9 Pralines Box | 990.0 | bestsellers, signature tab |
|
||||
| `pistachio-spread-60` | Pistachio Spread 60% | 825.0 | bestsellers, best-sellers tab |
|
||||
| `mozimo-almond-dragees-60` | Mozimo Almond Dragees 60% | 900.0 | bestsellers, best-sellers tab |
|
||||
| `kunafa-chocolate-bar` | Kunafa Chocolate Bar | 1600.0 | bestsellers, best-sellers tab |
|
||||
| `mozimo-carnival-celebration-s` | Mozimo Carnival Celebration (S) | 2400.0 | bestsellers, gifting, exquisite tab |
|
||||
| `mozimo-carnival-celebration-m` | Mozimo Carnival Celebration (M) | 3000.0 | gifting, exquisite tab |
|
||||
| `mozimo-carnival-celebration-l` | Mozimo Carnival Celebration (L) | 3900.0 | gifting, premium tab |
|
||||
| `mozimo-celestial-s` | Mozimo Celestial (S) | 4500.0 | gifting, premium tab |
|
||||
| `bars-box` | Bars Box | 1050.0 | gifting, signature tab |
|
||||
| `spreads-box` | Spreads Box | 1650.0 | gifting, best-sellers tab |
|
||||
| `single-origin-coco-nibs` | Single Origin Coco Nibs | 440.0 | pantry |
|
||||
| `cocoa-bean-shell` | Cocoa Bean Shell | 220.0 | pantry |
|
||||
| `mozimo-hot-chocolate-premix` | Mozimo Hot Chocolate Premix | 825.0 | pantry |
|
||||
| `mozimo-honey-comb` | Mozimo Honey Comb | 400.0 | pantry |
|
||||
|
||||
Also referenced in code: `rakhi-bark-box-200gm` (as `"/products/rakhi-bark-box-200gm"`)
|
||||
— **this handle does not exist in the live catalogue**. By contrast the live
|
||||
catalogue has **46 products that appear nowhere in `catalog.ts`**, including
|
||||
whole families with no code representation: the entire matcha line
|
||||
(`mozimo-matcha-bark`, `mozimo-matcha-collection-box`,
|
||||
`matcha-hazelnut-dragees-100gm`, `matcha-strawberry-praline`), the muesli line
|
||||
(`mozimo-chocolate-muesli-200gm` / `-400gm`), the 1 kg and 200 g bark boxes,
|
||||
`mozimo-celestial-l` (₹8500, the most expensive product), the praline boxes of
|
||||
25/36, the sugar-free bars and bark, and 15 of the 17 `best-sellers` entries.
|
||||
|
||||
---
|
||||
|
||||
## 5. Discounts, gift cards, selling plans
|
||||
|
||||
All of the following were queried explicitly against 2026-10.
|
||||
|
||||
| Thing | Query attempted | Result |
|
||||
| ------------------------ | -------------------------------------------------------------- | ------------------------------------------------------------------------------------------- |
|
||||
| Discount codes (list) | `discountCodes(first: 5)` on `QueryRoot` | **Field does not exist**: `Field 'discountCodes' doesn't exist on type 'QueryRoot'` |
|
||||
| Discount nodes (list) | `discountNodes(first: 5)` on `QueryRoot` | **Field does not exist**: `Field 'discountNodes' doesn't exist on type 'QueryRoot'` |
|
||||
| Gift cards (list) | `giftCards(first: 5)` on `QueryRoot` | **Field does not exist**: `Field 'giftCards' doesn't exist on type 'QueryRoot'` |
|
||||
| Gift card products | `Product.isGiftCard` over all 61 products | `false` × 61 — **no gift-card product exists** |
|
||||
| Selling plans | `Product.sellingPlanGroups(first: 5)` | ACCESS_DENIED — requires `unauthenticated_read_selling_plans` (minted token lacks it) |
|
||||
| Selling plans (indirect) | `Product.requiresSellingPlan` over all 61 products | `false` × 61 — no product requires a selling plan |
|
||||
| Cart discount codes | `Cart.discountCodes` (`CartDiscountCode { code, applicable }`) | Field exists on `Cart`; only reachable with a live cart id. No code values are enumerable |
|
||||
| Cart applied discounts | `Cart.discountApplications`, `Cart.appliedGiftCards` | Fields exist on `Cart`; only reachable with a live cart id |
|
||||
| Empty cart probe | `cartCreate(input: { lines: [] })` | Succeeded (id returned, no userErrors). Nothing configured can be read without adding lines |
|
||||
|
||||
**Findings, stated as absence where that is the evidence:**
|
||||
|
||||
- The Storefront API at 2026-10 exposes **no queryable list of discount codes
|
||||
and no queryable list/type of automatic discounts**. Discount data is only
|
||||
reachable per-cart (`Cart.discountCodes` / `Cart.discountApplications`).
|
||||
Whether the store has any discount configured is therefore **unverified** from
|
||||
the Storefront API — it cannot be enumerated.
|
||||
- **No gift cards**: no `QueryRoot.giftCards`, and no product with
|
||||
`isGiftCard: true`. This is a genuine absence, not a scope error.
|
||||
- **No subscription/selling plans in evidence**: `requiresSellingPlan` is `false`
|
||||
for all 61 products, and the one query that could confirm selling-plan groups
|
||||
is scope-blocked. The strongest honest statement is: no product requires a
|
||||
selling plan; selling-plan _groups_ are unverified (access denied).
|
||||
- `Shop.subscriptionPolicy` and `Shop.termsOfSale` exist as policies, which only
|
||||
indicates the store has a subscription policy text configured, not that any
|
||||
selling plan exists.
|
||||
|
||||
---
|
||||
|
||||
## 6. Metafield coverage
|
||||
|
||||
Coverage was measured by querying product metafields with an explicit
|
||||
`identifiers` list (the Storefront API's `Product.metafields` requires
|
||||
`identifiers` and cannot be paginated — `first:` is rejected with "does not
|
||||
accept argument 'first'", and empty identifiers return `[]`). The identifier
|
||||
list covered every key `src/lib/shopify.ts` queries, plus plausible variants.
|
||||
Any metafield key **outside** that list cannot be enumerated through the
|
||||
Storefront API and would show as absent here — flagged as **unverified**.
|
||||
|
||||
Keys queried: `custom.tagline`, `custom.ingredients`, `custom.ingredients_list`,
|
||||
`custom.storage`, `custom.energy_kcal`, `custom.protein_g`, `custom.total_fat_g`,
|
||||
`custom.saturated_fat_g`, `custom.carbohydrate_g`, `custom.sugar_g`,
|
||||
`custom.added_sugar_g`, `custom.sodium_mg`, `custom.nutrition`, `custom.weight`,
|
||||
`custom.allergens`, `custom.shelf_life`, `facts.nutrition`, `facts.ingredients`,
|
||||
`facts.storage`, `facts.allergens`, `facts.tagline`.
|
||||
|
||||
### Which keys are actually populated (product level)
|
||||
|
||||
| Key | Type | Products populated (of 61) |
|
||||
| ---------------------------------------------------------- | ------------------------ | -------------------------: |
|
||||
| `custom.ingredients` | `multi_line_text_field` | 20 |
|
||||
| `custom.energy_kcal` | `number_decimal` | 20 |
|
||||
| `custom.protein_g` | `number_decimal` | 20 |
|
||||
| `custom.total_fat_g` | `number_decimal` | 20 |
|
||||
| `custom.saturated_fat_g` | `number_decimal` | 20 |
|
||||
| `custom.sodium_mg` | `number_decimal` | 20 |
|
||||
| `custom.carbohydrate_g` | `number_decimal` | 20 |
|
||||
| `custom.sugar_g` | `number_decimal` | 18 |
|
||||
| `custom.added_sugar_g` | `number_decimal` | 17 |
|
||||
| `custom.tagline` | `single_line_text_field` | **1** |
|
||||
| `custom.storage` | — | 0 |
|
||||
| `custom.ingredients_list` | — | 0 |
|
||||
| `custom.nutrition` | — | 0 |
|
||||
| `custom.weight` / `custom.allergens` / `custom.shelf_life` | — | 0 |
|
||||
| `facts.nutrition` | — | **0** |
|
||||
| `facts.ingredients` | — | **0** |
|
||||
| `facts.storage` | — | **0** |
|
||||
| `facts.allergens` / `facts.tagline` | — | 0 |
|
||||
|
||||
The `facts.*` namespace is **completely empty across the catalogue**. The
|
||||
compat path in `shapeProduct` (`facts.nutrition` JSON array,
|
||||
`facts.ingredients`, `facts.storage`) will never fire for any product.
|
||||
|
||||
No collection-level metafields were populated either (the same identifier list
|
||||
was queried on all 16 collections; every entry was `null`).
|
||||
|
||||
### Products with NO metafields at all: 41 of 61
|
||||
|
||||
Products with zero populated metafields from the queried list, so both
|
||||
**ingredients and nutrition are absent** (those are the fields shown on product
|
||||
pages). All **41**, alphabetical — this is the same list that appears as `0/8` in
|
||||
the per-product table at the end of this section:
|
||||
|
||||
`1-kg-bark-box`, `200-gas-bark-box`, `9-pralines-box`, `bars-box`, `cocoa-bean-shell`, `kunafa-chocolate-bar`, `matcha-strawberry-praline`, `mozimo-almond-bark-45`, `mozimo-almond-bark-60`, `mozimo-almond-praline-bark`, `mozimo-assorted-bark-box`, `mozimo-carnival-celebration-l`, `mozimo-carnival-celebration-m`, `mozimo-carnival-celebration-s`, `mozimo-celestial-l`, `mozimo-celestial-s`, `mozimo-crunchy-granola-bark-45`, `mozimo-fruits-and-nuts-bark-60`, `mozimo-fruits-nuts-45`, `mozimo-gems-bark`, `mozimo-hazelnut-bark-sugar-free-45`, `mozimo-hazelnut-praline-bark`, `mozimo-honey-comb`, `mozimo-matcha-bark`, `mozimo-matcha-collection-box`, `mozimo-milk-45-tuilles`, `mozimo-nutty-bar`, `mozimo-pecan-bark`, `mozimo-praline-box-of-25`, `mozimo-praline-box-of-36`, `mozimo-praline-panetela`, `mozimo-praline-panetela-1`, `mozimo-raspberry-bark`, `mozimo-rice-crispy-bark`, `mozimo-salted-butter-crunch-bark-45`, `mozimo-white-chocolate-tuilles`, `mozimo-whole-hazelnuts-bark-45`, `mozimo-whole-hazelnuts-bark-60`, `pistachio-dragees`, `single-origin-coco-nibs`, `spreads-box`
|
||||
|
||||
### Products with ingredient + nutrition coverage: 20 of 61
|
||||
|
||||
All 20 carry `custom.ingredients` and a majority of the eight nutrient fields.
|
||||
`custom.tagline` is populated on exactly **one** product in the whole store:
|
||||
`4-pralines-box` = `"Roasted almonds coated in our 45% dark milk chocolate"`.
|
||||
|
||||
The 20 covered handles, alphabetical:
|
||||
|
||||
`4-pralines-box`, `matcha-hazelnut-dragees-100gm`, `mozimo-almond-dragees-45`,
|
||||
`mozimo-almond-dragees-60`, `mozimo-almond-slivers-rocks`,
|
||||
`mozimo-chocolate-muesli-200gm`, `mozimo-chocolate-muesli-400gm`,
|
||||
`mozimo-dark-chocolate-bar-60`, `mozimo-dark-chocolate-bar-72`,
|
||||
`mozimo-dark-chocolate-bar-85`, `mozimo-hazelnut-dragees-45`,
|
||||
`mozimo-hazelnut-dragees-60`, `mozimo-hazelnuts-spread`,
|
||||
`mozimo-hot-chocolate-premix`, `mozimo-milk-chocolate-bar-45`,
|
||||
`mozimo-orangettes`, `mozimo-pistachio-dragees`,
|
||||
`mozimo-sugar-free-dark-72-chocolate-bar`,
|
||||
`mozimo-sugar-free-milk-45-chocolate-bar`, `pistachio-spread-60`
|
||||
|
||||
Partial nutrition — fewer than 8 of 8 nutrient fields:
|
||||
|
||||
| handle | fields present | missing |
|
||||
| ----------------------------------------- | ------------------------------------------------------------------------------------------------- | ---------------------------------------- |
|
||||
| `mozimo-sugar-free-dark-72-chocolate-bar` | 6/8 — `energy_kcal`, `protein_g`, `total_fat_g`, `saturated_fat_g`, `carbohydrate_g`, `sodium_mg` | `custom.sugar_g`, `custom.added_sugar_g` |
|
||||
| `mozimo-sugar-free-milk-45-chocolate-bar` | 6/8 — same six | `custom.sugar_g`, `custom.added_sugar_g` |
|
||||
| `pistachio-spread-60` | 7/8 — adds `sugar_g` | `custom.added_sugar_g` |
|
||||
|
||||
Full nutrition — 17 products carry all 8 nutrient fields.
|
||||
|
||||
Sample of a complete record (`mozimo-dark-chocolate-bar-85`):
|
||||
|
||||
```
|
||||
custom.ingredients = "Cocoa Mass\nCocoa Butter\nSugar Grain\nSalt White\n"
|
||||
custom.energy_kcal = 485.66 custom.protein_g = 11.01
|
||||
custom.total_fat_g = 41.03 custom.saturated_fat_g = 24.57
|
||||
custom.carbohydrate_g= 42.39 custom.sugar_g = 14.9
|
||||
custom.added_sugar_g = 14.9 custom.sodium_mg = 38.76
|
||||
```
|
||||
|
||||
Sample of a partial record (`mozimo-sugar-free-dark-72-chocolate-bar`):
|
||||
|
||||
```
|
||||
custom.ingredients = "Cocoa Mass\nCocoa Butter\nErythritol\nStevia\nSalt White\n"
|
||||
custom.energy_kcal = 374.37 custom.protein_g = 9.15
|
||||
custom.total_fat_g = 35.46 custom.saturated_fat_g = 21.22
|
||||
custom.carbohydrate_g= 50.72 custom.sodium_mg = 38.76
|
||||
custom.sugar_g = (absent) custom.added_sugar_g = (absent)
|
||||
```
|
||||
|
||||
Per-product metafield table (all 61 products; `tagline` = `custom.tagline`,
|
||||
`ingr` = `custom.ingredients`, `nutr` = number of the 8 `custom.*` nutrient fields
|
||||
present, `facts` / `other` = any `facts.*` or other keys found):
|
||||
|
||||
| handle | tagline | ingr | nutr | facts.* | other keys |
|
||||
| ----------------------------------------- | ------- | ---- | ---- | ------- | ---------------- |
|
||||
| `1-kg-bark-box` | — | — | 0/8 | — | — |
|
||||
| `200-gas-bark-box` | — | — | 0/8 | — | — |
|
||||
| `4-pralines-box` | yes | yes | 8/8 | — | `custom.tagline` |
|
||||
| `9-pralines-box` | — | — | 0/8 | — | — |
|
||||
| `bars-box` | — | — | 0/8 | — | — |
|
||||
| `cocoa-bean-shell` | — | — | 0/8 | — | — |
|
||||
| `kunafa-chocolate-bar` | — | — | 0/8 | — | — |
|
||||
| `matcha-hazelnut-dragees-100gm` | — | yes | 8/8 | — | — |
|
||||
| `matcha-strawberry-praline` | — | — | 0/8 | — | — |
|
||||
| `mozimo-almond-bark-45` | — | — | 0/8 | — | — |
|
||||
| `mozimo-almond-bark-60` | — | — | 0/8 | — | — |
|
||||
| `mozimo-almond-dragees-45` | — | yes | 8/8 | — | — |
|
||||
| `mozimo-almond-dragees-60` | — | yes | 8/8 | — | — |
|
||||
| `mozimo-almond-praline-bark` | — | — | 0/8 | — | — |
|
||||
| `mozimo-almond-slivers-rocks` | — | yes | 8/8 | — | — |
|
||||
| `mozimo-assorted-bark-box` | — | — | 0/8 | — | — |
|
||||
| `mozimo-carnival-celebration-l` | — | — | 0/8 | — | — |
|
||||
| `mozimo-carnival-celebration-m` | — | — | 0/8 | — | — |
|
||||
| `mozimo-carnival-celebration-s` | — | — | 0/8 | — | — |
|
||||
| `mozimo-celestial-l` | — | — | 0/8 | — | — |
|
||||
| `mozimo-celestial-s` | — | — | 0/8 | — | — |
|
||||
| `mozimo-chocolate-muesli-200gm` | — | yes | 8/8 | — | — |
|
||||
| `mozimo-chocolate-muesli-400gm` | — | yes | 8/8 | — | — |
|
||||
| `mozimo-crunchy-granola-bark-45` | — | — | 0/8 | — | — |
|
||||
| `mozimo-dark-chocolate-bar-60` | — | yes | 8/8 | — | — |
|
||||
| `mozimo-dark-chocolate-bar-72` | — | yes | 8/8 | — | — |
|
||||
| `mozimo-dark-chocolate-bar-85` | — | yes | 8/8 | — | — |
|
||||
| `mozimo-fruits-and-nuts-bark-60` | — | — | 0/8 | — | — |
|
||||
| `mozimo-fruits-nuts-45` | — | — | 0/8 | — | — |
|
||||
| `mozimo-gems-bark` | — | — | 0/8 | — | — |
|
||||
| `mozimo-hazelnut-bark-sugar-free-45` | — | — | 0/8 | — | — |
|
||||
| `mozimo-hazelnut-dragees-45` | — | yes | 8/8 | — | — |
|
||||
| `mozimo-hazelnut-dragees-60` | — | yes | 8/8 | — | — |
|
||||
| `mozimo-hazelnut-praline-bark` | — | — | 0/8 | — | — |
|
||||
| `mozimo-hazelnuts-spread` | — | yes | 8/8 | — | — |
|
||||
| `mozimo-honey-comb` | — | — | 0/8 | — | — |
|
||||
| `mozimo-hot-chocolate-premix` | — | yes | 8/8 | — | — |
|
||||
| `mozimo-matcha-bark` | — | — | 0/8 | — | — |
|
||||
| `mozimo-matcha-collection-box` | — | — | 0/8 | — | — |
|
||||
| `mozimo-milk-45-tuilles` | — | — | 0/8 | — | — |
|
||||
| `mozimo-milk-chocolate-bar-45` | — | yes | 8/8 | — | — |
|
||||
| `mozimo-nutty-bar` | — | — | 0/8 | — | — |
|
||||
| `mozimo-orangettes` | — | yes | 8/8 | — | — |
|
||||
| `mozimo-pecan-bark` | — | — | 0/8 | — | — |
|
||||
| `mozimo-pistachio-dragees` | — | yes | 8/8 | — | — |
|
||||
| `mozimo-praline-box-of-25` | — | — | 0/8 | — | — |
|
||||
| `mozimo-praline-box-of-36` | — | — | 0/8 | — | — |
|
||||
| `mozimo-praline-panetela` | — | — | 0/8 | — | — |
|
||||
| `mozimo-praline-panetela-1` | — | — | 0/8 | — | — |
|
||||
| `mozimo-raspberry-bark` | — | — | 0/8 | — | — |
|
||||
| `mozimo-rice-crispy-bark` | — | — | 0/8 | — | — |
|
||||
| `mozimo-salted-butter-crunch-bark-45` | — | — | 0/8 | — | — |
|
||||
| `mozimo-sugar-free-dark-72-chocolate-bar` | — | yes | 6/8 | — | — |
|
||||
| `mozimo-sugar-free-milk-45-chocolate-bar` | — | yes | 6/8 | — | — |
|
||||
| `mozimo-white-chocolate-tuilles` | — | — | 0/8 | — | — |
|
||||
| `mozimo-whole-hazelnuts-bark-45` | — | — | 0/8 | — | — |
|
||||
| `mozimo-whole-hazelnuts-bark-60` | — | — | 0/8 | — | — |
|
||||
| `pistachio-dragees` | — | — | 0/8 | — | — |
|
||||
| `pistachio-spread-60` | — | yes | 7/8 | — | — |
|
||||
| `single-origin-coco-nibs` | — | — | 0/8 | — | — |
|
||||
| `spreads-box` | — | — | 0/8 | — | — |
|
||||
|
||||
## 7. Currency and markets
|
||||
|
||||
| Signal | Value |
|
||||
| --------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| Store currency | `INR` (from `paymentSettings.currencyCode`) |
|
||||
| Presentment currencies enabled | `["INR"]` — **one** |
|
||||
| `Shop.moneyFormat` | `Rs. {{amount}}` |
|
||||
| `Localization.country` | `IN`, currency `INR` (symbol `₹`) |
|
||||
| `Localization.language` | `EN` |
|
||||
| `availableCountries` | **only** `IN` (India) |
|
||||
| `availableLanguages` | **only** `EN` |
|
||||
| `shipsToCountries` | **29 codes** — `AE, AT, AU, BE, CA, CH, CZ, DE, DK, ES, FI, FR, GB, HK, IE, IL, IN, IT, JP, KR, MY, NL, NO, NZ, PL, PT, SE, SG, US` |
|
||||
| Currency code seen on every money field | `INR` on all 61 product prices, all 61 variant prices, and both price ranges — no second currency anywhere |
|
||||
|
||||
The store returns a **single currency (INR)** at a **single localization country
|
||||
(India)**. Shopify Markets is either not configured or not exposing any additional
|
||||
market through the Storefront API with this token: `enabledPresentmentCurrencies`
|
||||
has one entry, `availableCountries` has one entry, and no query returned a
|
||||
currency code other than `INR`.
|
||||
|
||||
There is one visible asymmetry: `Shop.shipsToCountries` lists 29 destinations
|
||||
including the `US`, `GB`, `AU`, `SG`, `AE` and most of the EU, while
|
||||
`Localization.availableCountries` exposes only `IN` and the shipping-policy body
|
||||
says the website "currently ships to addresses within India only" and "We are
|
||||
currently delivering Pan India". The Storefront API does not reconcile these.
|
||||
Whether a real multi-country market exists in admin is **unverified**; all the
|
||||
API will price or localize for is `INR`/`IN`.
|
||||
|
||||
---
|
||||
|
||||
## Appendix — what could not be determined
|
||||
|
||||
Stated explicitly rather than guessed:
|
||||
|
||||
1. **Discount codes / automatic discounts** — no Storefront query exists
|
||||
(`discountCodes`, `discountNodes` are undefined fields on `QueryRoot`).
|
||||
Presence or absence is **unverified**.
|
||||
2. **Selling-plan groups** — `Product.sellingPlanGroups` is ACCESS_DENIED with the
|
||||
minted token (`unauthenticated_read_selling_plans`). Only the negative signal
|
||||
`requiresSellingPlan: false` × 61 is available. **Unverified.**
|
||||
3. **Real payment providers / gateways, UPI, COD** — not exposed by the
|
||||
Storefront API at all. **Unverified.**
|
||||
4. **Metafield keys outside the queried identifier list** — `Product.metafields`
|
||||
cannot be paginated or enumerated at 2026-10; only known keys can be read.
|
||||
Any additional custom keys on products are invisible to this capture.
|
||||
**Unverified.**
|
||||
5. **Pickup locations** — `QueryRoot.locations` ACCESS_DENIED
|
||||
(`unauthenticated_read_product_pickup_locations`). **Unverified.**
|
||||
6. **Whether negative inventory is an intentional "made to order" model** — the
|
||||
API shows `quantityAvailable` of −1 to −20 with `availableForSale: true` on all
|
||||
61 products. The API does not explain it. **Not determinable.**
|
||||
7. **Whether a second Shopify Market exists in admin** — nothing on the
|
||||
Storefront API evidences it. **Unverified.**
|
||||
8. **`acceptedCardBrands` / `supportedDigitalWallets` being empty** — most likely
|
||||
a token-scope artifact, not a store setting. **Unverified.**
|
||||
9. **Static `SHOPIFY_STOREFRONT_ACCESS_TOKEN` token's scopes** — this capture used the minted token; the
|
||||
static token may have a different (possibly wider) scope set. **Unverified.**
|
||||
Reference in new issue
Block a user