← back to Dw Photo Capture
Photo capture: post each metafield with its store-defined type (fix 422)
c03988dc8aafd1d75fa46574a128bde3192a9420 · 2026-09-18 13:52:33 -0700 · steve
Live test caught a Shopify 422: mf() forced single_line_text_field for every
metafield, but custom.material is defined multi_line_text_field on the store, so
Shopify rejected the whole product create. Now fetch PRODUCT metafield definitions
once (cached) -> (ns.key)->type map, post each metafield with its declared type;
keys with no definition default to single_line (free-form, accepted). Only
custom.material was multi_line among the fields createNewItem writes.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DCq3132eTmJESAKqJBM8FN
Files touched
Diff
commit c03988dc8aafd1d75fa46574a128bde3192a9420
Author: steve <steve@designerwallcoverings.com>
Date: Fri Sep 18 13:52:33 2026 -0700
Photo capture: post each metafield with its store-defined type (fix 422)
Live test caught a Shopify 422: mf() forced single_line_text_field for every
metafield, but custom.material is defined multi_line_text_field on the store, so
Shopify rejected the whole product create. Now fetch PRODUCT metafield definitions
once (cached) -> (ns.key)->type map, post each metafield with its declared type;
keys with no definition default to single_line (free-form, accepted). Only
custom.material was multi_line among the fields createNewItem writes.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DCq3132eTmJESAKqJBM8FN
---
server.js | 29 ++++++++++++++++++++++++++++-
1 file changed, 28 insertions(+), 1 deletion(-)
diff --git a/server.js b/server.js
index 2c7af64..c74218d 100644
--- a/server.js
+++ b/server.js
@@ -2424,6 +2424,30 @@ function pgQuery(sql) {
});
}
+// ── Metafield TYPE map (store definitions) ───────────────────────────────────
+// A metafield value MUST be posted with the type its DEFINITION declares, or Shopify 422s the whole
+// product create (e.g. custom.material is defined multi_line_text_field on this store — forcing
+// single_line rejects the create). Fetch every PRODUCT definition once (cached 10m) → {"ns.key":type}.
+// (namespace,key) pairs with NO definition are free-form and safely accept single_line_text_field.
+let _mfTypeCache = { at: 0, map: null };
+async function getMetafieldTypeMap() {
+ if (_mfTypeCache.map && Date.now() - _mfTypeCache.at < 10 * 60 * 1000) return _mfTypeCache.map;
+ const map = {};
+ try {
+ let cursor = null, pages = 0;
+ do {
+ const r = await gql(`query($c:String){ metafieldDefinitions(first:250, ownerType:PRODUCT, after:$c){
+ pageInfo{ hasNextPage endCursor } edges{ node{ namespace key type{ name } } } } }`, { c: cursor });
+ const md = r.data && r.data.metafieldDefinitions;
+ if (!md) break;
+ for (const e of md.edges) map[`${e.node.namespace}.${e.node.key}`] = e.node.type.name;
+ cursor = md.pageInfo.hasNextPage ? md.pageInfo.endCursor : null;
+ } while (cursor && ++pages < 10);
+ } catch (e) { /* fall back to per-key default below */ }
+ _mfTypeCache = { at: Date.now(), map };
+ return map;
+}
+
// ── VENDOR-FIRST dropdown source: the canonical vendor_registry ──────────────
// Returns one row per real, active vendor with its canonical DW# minting inputs
// (sku_prefix + sku_range_start) and — best-effort — the short FileMaker `vid`
@@ -2592,7 +2616,10 @@ async function createNewItem(p, b64, dryRun) {
if (dryRun) return { ok: true, dryRun: true, preview };
// ── COMMIT: PostgreSQL-staging + Shopify draft + FileMaker master (draft-only, never published) ──
try {
- const mf = (ns, key, val) => val ? { namespace: ns, key, value: String(val), type: 'single_line_text_field' } : null;
+ // Each metafield MUST carry the type its store DEFINITION declares (e.g. custom.material is
+ // multi_line_text_field) or Shopify 422s the whole create. Undefined keys default to single_line.
+ const mfTypes = await getMetafieldTypeMap();
+ const mf = (ns, key, val) => val ? { namespace: ns, key, value: String(val), type: (mfTypes[`${ns}.${key}`] || 'single_line_text_field') } : null;
const variants = [{ option1: 'Roll', sku: dwsku, inventory_management: 'shopify', inventory_quantity: 0 },
{ option1: 'Sample', sku: dwsku + '-Sample', price: '4.25', inventory_management: 'shopify', inventory_quantity: 0 }];
if (price) variants[0].price = price;
← 37a2682 auto-data-snapshot: 2026-09-18T13:45:34 (2 data files) — dat
·
back to Dw Photo Capture
·
Photo capture: two-photo capture + mfr# identity gate (front 4cbb39e →