← Files WixARCHIVED FILE

skills/wix-app/references/stores/GET_PRODUCT.md

4.23 KB · Oct 8, 2026 · 12:02 UTC

↓ Download file

See the change to this file →

# Stores — Get Product

## Get a single product

```typescript
if (v === 'V3_CATALOG') {
  const product = await productsV3.getProduct(id);  // returns V3Product directly
  return product;
}
const { product } = await products.getProduct(id);  // V1 wraps in { product }
return product;
```

The V3 entity type is `productsV3.V3Product`.

---

## Product page URL

V3 `product.url` is populated only when the request's `fields` includes `'URL'` — `getProduct(id, { fields: ['URL'] })`, or the direct-call `searchProducts` / `queryProducts(query, { fields: ['URL'] })` (the fluent builder's `.find()` takes no options). V1 `productPageUrl` is an object, `{ base, path }`, not a string.

---

## Read product options and choices (V1 vs V3)

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` |

```typescript
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.

---

## Modifiers (V1 `manageVariants=false` options)

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.

```typescript
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);
    }
  }
}
```

---

## Variant choices (V1 object map → V3 array)

V1 `variant.choices` was a plain object map `{ "Size": "Small" }`. V3 restructures it as an array — accessing it with a key returns `undefined` silently.

```typescript
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(', '));
  }
}
```

SHA-256: 299e3c1c41608e4aa4f24a9acc5fa149151b726da7551a4b6f93ef285176b80e