Subchapter 2.86
references/stores/GET_PRODUCT.mdMarkdown4 KBView on GitHub
if (v === 'V3_CATALOG') {
const product = await productsV3.getProduct(id); // returns Product directly
return product;
}
const { product } = await products.getProduct(id); // V1 wraps in { product }
return product;V1 and V3 differ in both where choices live and what the choice fields are named. Getting this wrong causes TS2339 at validate time.
| V1 | V3 | |
|---|---|---|
| Options array on product | product.productOptions | product.options |
| Option name | option.name | option.name |
| Option type | option.optionType ("color" / "drop_down") | option.optionRenderType ("SWATCH_CHOICES" / "TEXT_CHOICES") |
| Choices array | option.choices ← directly on the option | option.choicesSettings?.choices ← nested |
| Choice display value | choice.value | choice.name |
| Choice color | choice.value (hex string) | choice.colorCode |
if (v === 'V3_CATALOG') {
const product = await productsV3.getProduct(id);
for (const option of product.options ?? []) {
const choices = option.choicesSettings?.choices ?? []; // ✅ nested — NOT option.choices / option.optionValues (TS2339)
for (const choice of choices) {
render(option.name, choice.name, choice.colorCode); // name = label; colorCode = hex for SWATCH_CHOICES
}
}
} else {
const res = await products.getProduct(id);
const product = res.product!; // product is optional in the raw type; ! matches SDK's strict-mode guarantee
for (const option of product.productOptions ?? []) {
for (const choice of option.choices ?? []) { // ✅ direct in V1
render(option.name, choice.value); // value = label or hex string
}
}
}In a site plugin on a product page: get productId from widget.getProp('product-id'), then call the appropriate version’s getProduct — same pattern as above.
In V3, options that don’t create variants (V1 manageVariants=false) live on product.modifiers, not product.options. They use the identical choicesSettings.choices pattern. Reading only product.options silently misses these.
if (v === 'V3_CATALOG') {
const product = await productsV3.getProduct(id);
// variant-creating options
for (const option of product.options ?? []) {
for (const choice of option.choicesSettings?.choices ?? []) {
render(option.name, choice.name, choice.colorCode);
}
}
// display-only / free-text customizations (formerly manageVariants=false)
for (const modifier of product.modifiers ?? []) {
for (const choice of modifier.choicesSettings?.choices ?? []) {
render(modifier.name, choice.name, choice.colorCode);
}
}
}V1 variant.choices was a plain object map { "Size": "Small" }. V3 restructures it as an array — accessing it with a key returns undefined silently.
if (v === 'V3_CATALOG') {
const product = await productsV3.getProduct(id);
// variants are NOT returned by queryProducts — use getProduct or Read-Only Variants API
for (const variant of product.variantsInfo?.variants ?? []) {
for (const c of variant.choices ?? []) {
render(c.optionChoiceNames?.optionName, c.optionChoiceNames?.choiceName);
}
// ❌ variant.choices['Size'] — always undefined in V3 (object map is gone)
}
} else {
const res = await products.getProduct(id);
const product = res.product!; // product is optional in the raw type; ! matches SDK's strict-mode guarantee
for (const variant of product.variants ?? []) {
// V1: choices is { [optionName]: value }
render(Object.entries(variant.choices ?? {}).map(([k, v]) => `${k}:${v}`).join(', '));
}
}