Setting the file. One moment. Wix Store Catalog · Wix Vibe Headless · wix/skills · Skills DocsWix Blog
references/storefront/app/rest/wix-store-catalog.js
JavaScript·146 lines·6 KB
14 * actualPriceRange.maxValue.formattedAmount {string} — highest price with currency symbol,
15 * compareAtPriceRange.minValue.formattedAmount {string} — strikethrough price (present when on sale),
16 * inventory.availabilityStatus {string} — "IN_STOCK"|"OUT_OF_STOCK"|"PARTIALLY_OUT_OF_STOCK",
17 * options {array} — product options e.g. Size, Color:
18 * [{ id, name, optionRenderType "TEXT_CHOICES"|"COLOR_CHOICES"|"SWATCH_CHOICES",
19 * choicesSettings.choices [{ choiceId, key, name, inStock, visible, linkedMedia }] }],
20 * modifiers {array} — non-variant customizations (engraving, gift wrap):
21 * [{ id, name, mandatory, modifierRenderType "TEXT_CHOICES"|"FREE_TEXT",
22 * key, choicesSettings.choices, freeTextSettings.key }],
23 * plainDescription {string} — product description as an HTML string (contains <p>, <br>,
24 * <strong>…) despite the "plain" name — NOT plain text. Render with innerHTML /
25 * dangerouslySetInnerHTML; strip tags only for plain-text contexts (meta description, teaser),
26 * variantsInfo.variants {array} — returned only by getProductBySlug:
27 * [{ id, visible, choices [{ optionChoiceIds: { optionId, choiceId } }],
28 * price: { actualPrice, compareAtPrice }, media, inventoryStatus: { inStock } }]
29 * To resolve a buyer's option selections to a variantId: find the variant whose choices
30 * match all selected { optionId, choiceId } pairs, then pass variant.id to addToCart.
31 *
32 * Category: { id, name, slug, visible, description, image, itemCounter, parentCategory.id }
33 * NB: queryCategories includes the auto-created system category { slug: "all-products" } —
34 * filter it out of a category menu (see INSTRUCTIONS.md). `visible` does not flag it.
35 * Full model: https://dev.wix.com/docs/api-reference/business-solutions/stores/catalog-v3/categories
36 */
37
38/**
39 * Query visible products (one page). Pass nextCursor back as cursor to load the next page.
40 * @param {{ limit?: number, cursor?: string }} [options]
41 * @returns {Promise<{ products: object[], nextCursor: string|null }>}
42 */
43export async function queryProducts({ limit = 100, cursor } = {}) {
44 const res = await wixApiRequest("/stores/v3/products/query", {
45 method: "POST",
46 body: {
47 fields: ["CURRENCY", "PLAIN_DESCRIPTION", "MEDIA_ITEMS_INFO"],
48 query: {
49 ...(cursor ? {} : { filter: { visible: true } }),
50 cursorPaging: cursor ? { limit, cursor } : { limit },
51 },
52 },
53 });
54 return {
55 products: res?.products ?? [],
56 nextCursor: res?.pagingMetadata?.cursors?.next ?? null,
57 };
58}
59
60/**
61 * Fetch a product by its URL slug. Returns null if not found.
62 * Returns the full product including variantsInfo.variants (with per-variant media and choices).
63 * @param {string} slug
64 * @returns {Promise<object|null>}
65 */
66export async function getProductBySlug(slug) {
67 const res = await wixApiRequest(`/stores/v3/products/slug/${encodeURIComponent(slug)}`, {
68 method: "GET",
69 query: { fields: ["CURRENCY", "PLAIN_DESCRIPTION", "MEDIA_ITEMS_INFO"] },
70 });
71 return res?.product ?? null;
72}
73
74/**
75 * Query visible products belonging to a category (one page).
76 * @param {string} categoryId Category GUID from queryCategories / getCategoryBySlug.
77 * @param {{ limit?: number, cursor?: string }} [options]
78 * @returns {Promise<{ products: object[], nextCursor: string|null }>}
79 */
80export async function queryProductsByCategory(categoryId, { limit = 100, cursor } = {}) {
81 const res = await wixApiRequest("/stores/v3/products/search", {
82 method: "POST",
83 body: {
84 fields: ["CURRENCY", "PLAIN_DESCRIPTION", "MEDIA_ITEMS_INFO"],
85 search: {
86 ...(cursor
87 ? { cursorPaging: { limit, cursor } }
88 : {
89 cursorPaging: { limit },
90 filter: {
91 visible: true,
92 "allCategoriesInfo.categories": { $matchItems: [{ id: categoryId }] },
93 },
94 }),
95 },
96 },
97 });
98 return {
99 products: res?.products ?? [],
100 nextCursor: res?.pagingMetadata?.cursors?.next ?? null,
101 };
102}
103
104/**
105 * Total number of visible products. Used for empty-state logic (0 → prompt user to add products).
106 * @returns {Promise<number>}
107 */
108export async function countProducts() {
109 const res = await wixApiRequest("/stores/v3/products/count", {
110 method: "POST",
111 body: { filter: { visible: true } },
112 });
113 return res?.count ?? 0;
114}
115
116/**
117 * Query Wix Stores categories (one page).
118 * @param {{ limit?: number, cursor?: string }} [options]
119 * @returns {Promise<{ categories: object[], nextCursor: string|null }>}
120 */
121export async function queryCategories({ limit = 100, cursor } = {}) {
122 const res = await wixApiRequest("/categories/v1/categories/query", {
123 method: "POST",
124 body: {
125 treeReference: { appNamespace: "@wix/stores", treeKey: null },
126 query: { cursorPaging: cursor ? { limit, cursor } : { limit } },
127 },
128 });
129 return {
130 categories: res?.categories ?? [],
131 nextCursor: res?.pagingMetadata?.cursors?.next ?? null,
132 };
133}
134
135/**
136 * Get a single category by its URL slug. Returns null if not found.
137 * @param {string} slug
138 * @returns {Promise<object|null>}
139 */
140export async function getCategoryBySlug(slug) {
141 const res = await wixApiRequest(`/categories/v1/categories/slug/${encodeURIComponent(slug)}`, {
142 method: "GET",
143 query: { "treeReference.appNamespace": "@wix/stores" },
144 });
145 return res?.category ?? null;
146}