The AFK half of the wayfinder ticket "Seed the dev store from the live catalogue". Products, metafields and images still come from the CSV export — the CSV re-hosts images for free — but collection membership cannot: the Collection column is import-only, takes one collection per product, and this catalogue runs 1.6 memberships per product across 16 collections. - scripts/seed-dev-store.mjs (`npm run seed:dev-store`): creates each missing collection with its members in one call and publishes it. Uses CollectionCreateInput.sources because collectionAddProducts is deprecated at 2026-10, and publishes because collectionCreate leaves a collection unpublished — invisible to the Storefront API the dev store exists to serve. Idempotent, never deletes, reports handles it cannot find (which is how a partial CSV import shows up), and is a dry run unless given --apply. - assets/dev-store-seed.json: the baseline the seed is checked against and the membership map it consumes — 61 products with option values, weights and product types, 16 collections with 100 memberships, and the 10 metafield definitions that must exist before the import. - The ticket now records what the probes settled: the option shape is readable from the Storefront API (all 61 use Option1 name=Title; 22 are `Default Title`, 39 carry real values, so the import must not normalise), and the repo's production credentials are Storefront-scoped only — `products`, `collections` and `productsCount` are Access denied without read_products — so the ticket's "Admin API script using the credentials the repo already mints" was never available. A dev-store custom app is required instead.
11 KiB
Seed the dev store from the live catalogue
Type: task (HITL)
Status: claimed
Part of the map. 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.ingredientsas multi-line text andcustom.energy_kcalas 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
- Export the full catalogue from production and record what
Option1 name/Option1 valueactually contain. - Create the metafield definitions in the dev store before importing.
- Run a small-batch import with Overwrite off to surface per-line errors cheaply, then the full set.
- 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.
- 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
Comments
AFK half worked — and the seeding decision re-opens further
Progress against To resolve: #1 is settled without an export; #4's stated option is dead and needs a decision the agent cannot make alone.
1. Variant option shape — settled (To resolve #1) ✅
The Storefront API exposes Product.options, so the export is not needed to read the
shape. All 61 products carry one option named Title. The values are not uniform:
Option1 value (= Variant title) |
products |
|---|---|
Default Title |
22 |
100gm |
17 |
200gm |
11 |
60gm |
5 |
1 |
3 |
250gm, 150gm, 12pc |
1 each |
So the sample CSV's shape (name Title, a real value) covers 39 of 61 and the docs'
Default Title covers 22. The import must preserve each product's own value: changing
an option value deletes and recreates the Variant, and the resulting Variant GIDs are
what Phase 1 carts. Do not normalise. (ADR-0002's claim that weight lives in the
Variant title holds for 39 products, not all 61; 22 are Default Title.)
2. The repo's credentials cannot drive an Admin API seed (To resolve #4) ❌
The production app's granted scopes are Storefront-only:
unauthenticated_read_product_listings unauthenticated_read_product_inventory
unauthenticated_read_product_tags unauthenticated_read_checkouts
unauthenticated_write_checkouts
products, productsCount and collections all return
Access denied … Required access: 'read_products' access scope. So
"a small Admin API script using the credentials the repo already mints" is not
available as written. Any Admin API mechanism needs a custom app installed in the
destination store with write_products.
Two consequences beyond this ticket:
- Production's collection structure (manual vs smart, and any smart rules) is also unreadable with today's scopes, so a collection rebuild can only reproduce today's membership as manual collections — a real fidelity gap, not a bug.
- The Phase 0 admin data fixes (61 ×
productType, 41 × weight) could be scripted rather than hand-edited if an Admin-scoped app is installed on production.
3. Metafield definitions to create first (To resolve #2) — 10 keys
The Storefront API cannot enumerate metafields (metafields demands explicit
identifiers), so this comes from docs/shopify-catalog-inventory.md §6, measured with
an explicit identifier list. These are the populated keys, i.e. the columns the CSV will
actually carry:
| definition | type | populated |
|---|---|---|
custom.ingredients |
multi_line_text_field |
20 |
custom.energy_kcal, custom.protein_g, custom.total_fat_g, custom.saturated_fat_g, custom.carbohydrate_g, custom.sodium_mg |
number_decimal |
20 each |
custom.sugar_g |
number_decimal |
18 |
custom.added_sugar_g |
number_decimal |
17 |
custom.tagline |
single_line_text_field |
1 |
The facts.* namespace is empty catalogue-wide, so no facts.* definitions are needed
for a faithful seed (ticket 09 decides whether the compat branch survives at all).
4. Collection membership is 16 collections / 100 memberships
Recorded with the full product list, option values, weights and product types in
assets/dev-store-seed.json — the baseline the seed is
checked against, and the membership map whichever mechanism is chosen will consume.
Nothing in the CSV path can carry this. The docs confirm the Collection column takes
one collection per product, is never exported, and — per the overwrite rules — a
blank non-required column overwrites the existing value as blank. Repeated imports with
different Collection values would have to accumulate membership, which is
undocumented and whose failure mode is destroying membership. Not a sound mechanism.
5. The second mechanism, at 2026-10
collectionAddProductsis deprecated; the current path iscollectionUpdatewithinclusion.selectionsToAdd, orcollectionCreatewithsources[].inclusion.selections. Both needwrite_products.- Trap:
collectionCreateleaves the collection unpublished, so without apublishablePublishthe membership work is invisible to the Storefront API — the thing the dev store exists to serve.
Decision: CSV for products, a dev-store app for collections ✅
Products, metafields and images stay on CSV — it re-hosts images for free, whereas an API
seed would need staged uploads for 61 products. Collections go through a custom app
installed in the dev store, driven by
scripts/seed-dev-store.mjs, which creates each
collection with its members in one call and publishes it. Chosen over hand-building
because the membership map is a snapshot that will drift, and over an all-API seed because
images would then need staging. It also makes the collection step re-runnable whenever
production changes.
Fidelity gap, recorded rather than hidden: production's collection structure is unreadable with today's scopes, so the rebuild reproduces today's membership as manual collections. If any of the 16 is a smart collection in production, the dev store will hold a manual copy of it.
Runbook (HITL — the parts that need admin access)
In the dev store staging-iw0gviik.myshopify.com:
- Create the custom app. Settings → Apps and sales channels → Develop apps → Create
an app → Configure Admin API scopes:
read_products,write_products,read_publications,write_publications→ Install app → copy the Admin API access token (shpat_…).write_productscoverscollectionCreate;write_publicationsandread_publicationscoverpublishablePublishand thepublicationsquery. - Create the metafield definitions (Settings → Custom data → Products) before importing — the 10 keys and types in §3 above. Undefined metafields on import are undocumented, which is why this comes first.
- Export from production, then import. Products → Export → Plain CSV → Export
products (an All products export is emailed, not downloaded). Then Products → Import
→ Overwrite products with matching handles, small batch first. Leave
Option1 name/Option1 valueexactly as exported (§1). Images are URLs only: production's products must stay publicly reachable until the import finishes. - Add the dev-store credentials to
.env(gitignored):SHOPIFY_DEV_STORE_DOMAIN,SHOPIFY_DEV_ADMIN_TOKEN. - Run the collection seed — dry run, then apply:
npm run seed:dev-store, thennpm run seed:dev-store -- --apply. It creates the 16 collections, adds the 100 memberships and publishes each one; existing handles are skipped and any product handle it cannot find is reported, which is how a partial CSV import shows up. - Record what landed (To resolve #5): product count, collection count and
membership, metafield coverage, anything rebuilt by hand — and whether the Variant GIDs
differ from
assets/dev-store-seed.json(they will).
Unverified until step 5 runs: the script's mutation shapes come from the 2026-10 Admin
reference (it uses the CollectionCreateInput.sources form because collectionAddProducts
is deprecated). Its auth, error and snapshot paths have been exercised against production;
the writes have not, because no dev-store app exists yet. That is why the dry run is the
default.
Facts later tickets depend on once this is done: which products exist in the dev store, the dev store's own Variant GIDs, and whether the metafield values survived the import.