@wix/agent-skills 1.16.3 → 1.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/package.json +1 -1
  2. package/skills/wix-app/references/EDITOR_REACT_COMPONENT.md +134 -127
  3. package/skills/wix-app/references/SITE_PLUGIN.md +2 -2
  4. package/skills/wix-app/references/backend-event/COMMON-EVENTS.md +2 -2
  5. package/skills/wix-app/references/editor-react-component/ACCESSIBILITY.md +182 -99
  6. package/skills/wix-app/references/editor-react-component/ANIMATED-COMPONENTS.md +92 -48
  7. package/skills/wix-app/references/editor-react-component/BRANDED-COMPONENTS.md +15 -6
  8. package/skills/wix-app/references/editor-react-component/COMPONENT-CONTRACT.md +275 -0
  9. package/skills/wix-app/references/editor-react-component/COMPONENT-PREVIEW.md +29 -42
  10. package/skills/wix-app/references/editor-react-component/CSS-GUIDELINES.md +153 -449
  11. package/skills/wix-app/references/editor-react-component/DESIGN-STATES.md +53 -29
  12. package/skills/wix-app/references/editor-react-component/DIRECTIONALITY.md +53 -158
  13. package/skills/wix-app/references/editor-react-component/EDITOR-EXTENSION-CONFIGURATION.md +78 -0
  14. package/skills/wix-app/references/editor-react-component/FUNCTION-HANDLERS.md +42 -134
  15. package/skills/wix-app/references/editor-react-component/MANIFEST-ERRORS.md +11 -3
  16. package/skills/wix-app/references/editor-react-component/PARTS.md +46 -24
  17. package/skills/wix-app/references/editor-react-component/PROPS-VS-CSS.md +63 -179
  18. package/skills/wix-app/references/editor-react-component/REACT-GUIDELINES.md +79 -163
  19. package/skills/wix-app/references/editor-react-component/SITE-CONTEXT-HOOKS.md +36 -21
  20. package/skills/wix-app/references/editor-react-component/SSR.md +30 -99
  21. package/skills/wix-base44-connector/SKILL.md +34 -17
  22. package/skills/wix-base44-connector/scripts/utils.js +33 -25
  23. package/skills/wix-headless/entry/bootstrap.mjs +9 -2
  24. package/skills/wix-headless/references/SDK_HANDOFF.md +1 -1
  25. package/skills/wix-headless/references/inline-recipes/how-to-code-a-store.md +42 -23
  26. package/skills/wix-headless/references/inline-recipes/how-to-code-pricing-plans.md +10 -8
  27. package/skills/wix-headless/references/inline-recipes/how-to-code-restaurant-orders.md +27 -24
  28. package/skills/wix-headless/references/managed/AUTHENTICATION.md +5 -0
  29. package/skills/wix-manage/SKILL.md +11 -1
  30. package/skills/wix-manage/references/bookings/booking-system-integration-gaps.md +20 -13
  31. package/skills/wix-manage/references/bookings/end-to-end-booking-flow.md +24 -11
  32. package/skills/wix-manage/references/cms/cms-ecommerce-catalog-integration.md +5 -5
  33. package/skills/wix-manage/references/seo/manage-seo-tags.md +315 -0
  34. package/skills/wix-manage/references/seo/manage-url-redirects.md +250 -0
  35. package/skills/wix-manage/references/sites/query-sites.md +72 -161
  36. package/skills/wix-replatform/resources/rp-mapper/wix-business-solutions-taxonomy.md +5 -6
  37. package/skills/wix-vibe-headless/platforms/base44-ecom-light-seed.md +13 -9
  38. package/skills/wix-vibe-headless/platforms/base44-ecom-light.md +10 -7
  39. package/skills/wix-vibe-headless/platforms/base44.md +13 -10
  40. package/skills/wix-vibe-headless/references/bookings/INSTRUCTIONS.md +2 -0
  41. package/skills/wix-vibe-headless/references/bookings/app/rest/wix-bookings-checkout.js +9 -8
  42. package/skills/wix-vibe-headless/references/restaurants/INSTRUCTIONS.md +4 -1
  43. package/skills/wix-vibe-headless/references/restaurants/app/components/OrderCartDrawer.jsx +23 -11
  44. package/skills/wix-vibe-headless/references/restaurants/app/context/OrderCartContext.jsx +1 -1
  45. package/skills/wix-vibe-headless/references/restaurants/app/rest/wix-restaurants-ordering.js +29 -31
  46. package/skills/wix-vibe-headless/references/storefront/INSTRUCTIONS.md +15 -8
  47. package/skills/wix-vibe-headless/references/storefront/app/components/CartDrawer.jsx +37 -38
  48. package/skills/wix-vibe-headless/references/storefront/app/context/CartContext.jsx +2 -2
  49. package/skills/wix-vibe-headless/references/storefront/app/rest/wix-store-cart.js +35 -35
  50. package/skills/wix-app/references/editor-react-component/A11Y-REVIEW-REFERENCE.md +0 -172
  51. package/skills/wix-app/references/editor-react-component/A11Y-REVIEW.md +0 -143
  52. package/skills/wix-app/references/editor-react-component/COMPONENT-API.md +0 -516
  53. package/skills/wix-app/references/editor-react-component/COMPONENT-CONFIGURATION.md +0 -225
  54. package/skills/wix-app/references/editor-react-component/EDIT-FLOW.md +0 -6
  55. package/skills/wix-app/references/editor-react-component/REACT-PATTERNS.md +0 -95
@@ -1,7 +1,6 @@
1
- // Wix docs helpers for the Base44 sandbox — the scratch-lane set. Small results return
2
- // inline; anything over ~4,000 chars (exec results clip at ~5,000) is saved under the
3
- // scratch dir and comes back as { path, bytes, lines, outline } with the read tools
4
- // pre-pointed at it.
1
+ // Wix docs helpers for the Base44 sandbox. Small results return inline; anything over
2
+ // ~4,000 chars (exec results clip at ~5,000) is saved under .agents/skills/wix-base44-connector/tmp and comes
3
+ // back as { path, bytes, lines, outline } with the read tools pre-pointed at it.
5
4
  //
6
5
  // Load per exec (execs share no state) — from disk, network only as first-touch fallback:
7
6
  // const fs = require("fs"), P = ".agents/skills/wix-base44-connector/utils.js";
@@ -14,14 +13,13 @@
14
13
  // to find, read_file(<path>) with offset/limit to window (numbered lines, 45K cap — no exec
15
14
  // round needed), pipelines for the rest (GNU grep/sed; awk is mawk; no rg).
16
15
  //
17
- // API responses are site data and stay OUT of scratch (scratch ships with the app):
18
- // post() never saves — project responses to facts.
16
+ // API responses are site data and never saved — post() projects them to facts.
19
17
 
20
18
  const fs = require("fs");
21
19
  const path = require("path");
22
20
 
23
21
  const BUDGET = 4000;
24
- const SCRATCH = ".agents/skills/wix-base44-connector/scratch";
22
+ const SCRATCH = ".agents/skills/wix-base44-connector/tmp";
25
23
 
26
24
  const clip = (out) => {
27
25
  if (typeof out === "string")
@@ -48,7 +46,7 @@ async function post(url, body, token) {
48
46
  }
49
47
 
50
48
  function save(name, text) {
51
- const dir = path.join(process.cwd(), SCRATCH);
49
+ const dir = SCRATCH;
52
50
  fs.mkdirSync(dir, { recursive: true });
53
51
  fs.writeFileSync(path.join(dir, name), text);
54
52
  return { path: SCRATCH + "/" + name, bytes: Buffer.byteLength(text), lines: text.split("\n").length };
@@ -60,15 +58,18 @@ const outlineOf = (lines, cap = 30) => {
60
58
  return { outline: heads.slice(0, cap), outlineOmitted: Math.max(0, heads.length - cap) };
61
59
  };
62
60
 
63
- // A ref is a scratch path or the URL it came from — resolve to lines, fetching+saving
61
+ // A ref is a saved path or the URL it came from — resolve to lines, fetching+saving
64
62
  // URLs on first touch (the .md suffix is appended for extensionless docs URLs).
65
- const slugOf = (url) => url.replace(/\?.*$/, "").split("/").pop().replace(/\.md$/, "") + ".md";
63
+ // two path segments, not one: doc leaves repeat across products (every API has an
64
+ // introduction.md), and a one-segment name makes resolveRef's cache return the WRONG document
65
+ const slugOf = (url) => url.replace(/\?.*$/, "").replace(/\.md$/, "")
66
+ .split("/").filter(Boolean).slice(-2).join("-") + ".md";
66
67
  async function resolveRef(ref) {
67
68
  const isUrl = /^https?:/.test(ref);
68
69
  const name = isUrl ? slugOf(ref) : ref.split("/").pop();
69
- const file = path.join(process.cwd(), SCRATCH, name);
70
+ const file = path.join(SCRATCH, name);
70
71
  if (fs.existsSync(file)) return { path: SCRATCH + "/" + name, lines: fs.readFileSync(file, "utf8").split("\n") };
71
- if (!isUrl) throw new Error("no such scratch file: " + ref + " — pass a returned path or the source URL");
72
+ if (!isUrl) throw new Error("no such saved file: " + ref + " — pass a returned path or the source URL");
72
73
  const mdUrl = /\.[a-z]{2,5}$/.test(ref.replace(/\?.*$/, "")) ? ref : ref.replace(/\?.*$/, "") + ".md";
73
74
  const res = await fetch(mdUrl);
74
75
  if (!res.ok) throw new Error(res.status + " on " + mdUrl + " — not a docs page; take URLs from output, don't compose");
@@ -109,7 +110,8 @@ async function browse(menuUrl, { include, filter, depth } = {}) {
109
110
  }
110
111
 
111
112
  // Semantic search — ranks, never says "no match". The reduced hits come back inline
112
- // AND the full raw content is saved for grep/window follow-ups.
113
+ // AND the full raw content is saved for grep/window follow-ups. { type } picks the portal:
114
+ // REST (default) · WIX_HEADLESS.
113
115
  async function search(term, { type = "REST", max = 5, lines = 6 } = {}) {
114
116
  const { content } = await post("https://www.wixapis.com/mcp-docs-search/v1/docs/search/markdown",
115
117
  { search_term: term, document_type: type, maximum_results: max, lines_in_each_result: lines });
@@ -146,7 +148,7 @@ async function page(url) {
146
148
 
147
149
  // ── shell ─────────────────────────────────────────────────────────────────────
148
150
 
149
- // Compose native pipelines over scratch — grep -n, sed -n, mawk, sort, uniq, wc
151
+ // Compose native pipelines over .agents/skills/wix-base44-connector/tmp — grep -n, sed -n, mawk, sort, uniq, wc
150
152
  // (GNU grep/sed; awk is mawk; no rg). Cap your own output (| head -40); the return
151
153
  // clips regardless. grep's exit 1 means no match, not failure — the return says so.
152
154
  function bash(cmd) {
@@ -165,29 +167,35 @@ function bash(cmd) {
165
167
 
166
168
  // ── the spec index ────────────────────────────────────────────────────────────
167
169
 
168
- // Run a query over the API spec index. In scope: lightIndex (RESOURCES with
169
- // .methods — operationId, summary, httpMethod, path [PARTIAL — never call it],
170
- // publicUrl [callable], docsUrl) and getResourceSchemaByUrl(docsUrl). Arrive with
171
- // a docsUrl and match by it — also the only proof an API does NOT exist.
172
- // A big result is saved as JSON; grep it for the keys you saw in its head.
170
+ // Inspect a located method's schema — request body, responses, enums, and the
171
+ // filterable-fields map. In scope: lightIndex (RESOURCES with .methods — operationId,
172
+ // summary, httpMethod, path [PARTIAL — never call it], publicUrl [callable], docsUrl)
173
+ // and getResourceSchemaByUrl(docsUrl) → s.methods (each with requestBody, responses,
174
+ // legacyExamples, queryMethodData.queryFieldsCapabilitiesMap; $circular via
175
+ // s.components.schemas). Arrive with a docsUrl from search/browse — not a discovery
176
+ // tool. A big result is saved as JSON; grep it for the keys you saw in its head.
173
177
  async function spec(code) {
174
178
  if (!/^\s*async function/.test(code)) code = `async function(){ ${code} }`;
175
179
  const { result } = await post("https://mcp.wix.com/api/code-mode/search", { code });
176
180
  if (result == null || (Array.isArray(result) && !result.length))
177
- return { result, note: "empty — the query missed the shape, not proof of absence; find the resource by docsUrl on lightIndex" };
181
+ return { result, note: "empty — the query missed the shape; match your docsUrl against s.methods[].docsUrl" };
178
182
  const text = JSON.stringify(result, null, 1);
179
183
  if (text.length <= BUDGET) return result;
180
- return { ...save("spec.json", text),
184
+ let h = 5381;
185
+ for (const ch of code) h = ((h * 33) ^ ch.charCodeAt(0)) >>> 0; // same query → same file
186
+ return { ...save("spec-" + h.toString(36) + ".json", text),
181
187
  shape: Array.isArray(result) ? `Array(${result.length})` : Object.keys(result || {}).slice(0, 15),
182
188
  head: text.slice(0, 600) };
183
189
  }
184
190
 
185
191
  // ── management recipes ────────────────────────────────────────────────────────
186
192
 
187
- // ~100 curated multi-step admin flows. No arg → categories with counts; a category
188
- // name → its recipes; any other term → search every recipe's name + gist for it.
193
+ // ~100 curated multi-step MANAGEMENT (admin) flows across 23 categories — ecommerce,
194
+ // bookings, stores, cms, contacts, sites, get-paid, marketing, pricing-plans, events,
195
+ // blog, forms, restaurants, domains, media, … No arg → categories with counts; a
196
+ // category name → its recipes; any other term → search every recipe's name + gist.
189
197
  // Read the chosen url with page(url), then grep/window/fields.
190
- async function recipes(q) {
198
+ async function mgmtRecipes(q) {
191
199
  const { base, files } = await (await fetch("https://dev.wix.com/docs/skills/manage.manifest.json")).json();
192
200
  const cat = f => (f.path.match(/^references\/([^/]+)\//) || [])[1];
193
201
  const row = f => ({ name: f.name, cat: cat(f), gist: (f.description || "").slice(0, 120),
@@ -209,4 +217,4 @@ async function recipes(q) {
209
217
  return clip(m.map(row));
210
218
  }
211
219
 
212
- module.exports = { post, clip, context, browse, search, page, bash, spec, recipes };
220
+ module.exports = { post, clip, context, browse, search, page, bash, spec, mgmtRecipes };
@@ -40,7 +40,14 @@ function checkCli() {
40
40
  emit('cli_ok', { version });
41
41
  }
42
42
 
43
- // ── 2. Login (human-in-the-loop device code; forward the CLI's own events) ───
43
+ // ── 2. Reuse an existing session, or start device login ──────────────────────
44
+ function hasExistingSession() {
45
+ const r = capture(WIX[0], [...WIX.slice(1), 'whoami']);
46
+ if (r.status !== 0) return false;
47
+ emit('logged_in');
48
+ return true;
49
+ }
50
+
44
51
  function login() {
45
52
  return new Promise((resolve) => {
46
53
  const child = spawn(WIX[0], [...WIX.slice(1), 'login'], { shell: isWin, env: AGENT_ENV });
@@ -90,4 +97,4 @@ function login() {
90
97
 
91
98
  // ── main ────────────────────────────────────────────────────────────────────
92
99
  checkCli();
93
- await login();
100
+ if (!hasExistingSession()) await login();
@@ -68,7 +68,7 @@ Everything else the host resolves live from the queries in §3. (Static platform
68
68
  - Frontend CMS access is **read**; visitor writes go through **Forms** submissions.
69
69
  - **Blog comments are member-gated — read public, write authenticated.** Querying/rendering comments is public, so list them server-side in SSR. **Submitting** needs a logged-in member, so don't gate the form behind an upfront login check (that branch is what burns deliberation): render the form always and resolve identity at submit — POST to a backend endpoint (`src/pages/api/*.ts`) that calls `createComment` with the request session; if the caller isn't a member, redirect to the built-in `/api/auth/login?returnUrl=…`. **Do not build the comment form as a client island** — the API-endpoint + session path is the documented shape and avoids the browser-auth detour. The comment API keys (post `referenceId`, Blog appDefId, author lookup via `post.memberId`) are in `how-to-code-a-blog.md`.
70
70
  - **Member auth is one mechanism split on the frontend axis (the §3 members note routes to the right recipe), and orthogonal to elevation.** Sign-up and log-in are the *same* flow (the Wix login page logs in **or** registers); log-out is its inverse. Two layers stay separate: **identity** (logged-in vs not — no app install) vs **profile** (name/photo/roles — needs the **Wix Members Area app** installed, `SETUP.md`). **pricing-plans is a hard dependency**: subscribing requires a logged-in member; for the other verticals, member login is a soft add-on for their "my …" surfaces only. **A member reading their own data (own orders/bookings/subscriptions, plan-gated content) uses the member token with NO `auth.elevate`** — elevation is the separate admin/permission axis (site-wide reads, server-side only), not something member features need.
71
- - **Gating is a LIVE signal, never a hardcoded id list.** Whenever content is gated (members-only articles, plan-eligible class booking, any "premium" surface), decide eligibility from a signal read **at request time** — a queryable flag carried on the content (a `members-only` blog category/tag or a boolean CMS field) and live coverage/eligibility (`checkout.membershipOptions.eligibleMemberships`, or the member's active-order `planId`s matched against **live** Benefit-Program coverage). **Never** gate on a frozen set of "premium" slugs/ids or a seed-time `plan→service` map: a gated post or a newly-covered service the owner adds later would silently read as public/ineligible. See `how-to-code-a-blog.md` (member features) and `how-to-code-pricing-plans.md` (coverage read).
71
+ - **Gating is a LIVE signal, never a hardcoded id list.** Whenever content is gated (members-only articles, plan-eligible class booking, any "premium" surface), decide eligibility from a signal read **at request time** — a queryable flag carried on the content (a `members-only` blog category/tag or a boolean CMS field) and live coverage/eligibility (Cart V2's `currentCartV2.calculateCurrentCart()` → `summary.paymentSummary.memberships`, or the member's active-order `planId`s matched against **live** Benefit-Program coverage). **Never** gate on a frozen set of "premium" slugs/ids or a seed-time `plan→service` map: a gated post or a newly-covered service the owner adds later would silently read as public/ineligible. See `how-to-code-a-blog.md` (member features) and `how-to-code-pricing-plans.md` (coverage read).
72
72
 
73
73
  ### 6 · What a complete site must include (per loaded capability)
74
74
  For each loaded capability, carry its **Required site features** and **Implementation checklist** from `references/CAPABILITIES.md` into the guide — in plain product language, lightly tailored to what was seeded. **This is the build spec, not optional polish:** the host should build every *required feature* and cover every *checklist* item. For example, a blog must show the **author** (name + photo), the publish date and reading time, the cover image, and the **full formatted content** (not flattened text) — a posts-list-plus-plain-text-body is incomplete. The host maps these onto its own components using the packages/docs in §3 and the seeded schema in §4.
@@ -24,22 +24,24 @@ A concise contract for writing the **frontend code** of a storefront against a C
24
24
  | Products (list, get, search, filter) | `@wix/stores` | `productsV3` |
25
25
  | Variants (to resolve `variantId`) | `@wix/stores` | `readOnlyVariantsV3` |
26
26
  | Categories | `@wix/stores` | `categories` |
27
- | Cart (add / get / checkout) | `@wix/ecom` | `currentCart` |
27
+ | Cart (add / get / checkout) | `@wix/ecom` | `currentCartV2` |
28
28
  | Redirect to hosted checkout | `@wix/redirects` | `redirects` |
29
29
 
30
+ > Migrating from Cart V1 / Checkout V1? The code below is V2-only — see the [migration guide](https://dev.wix.com/docs/api-reference/business-solutions/e-commerce/purchase-flow/cart-v2/migration-guide) for the before/after.
31
+
30
32
  **Never** import the V1 `products` or `collections` modules from `@wix/stores`.
31
33
 
32
34
  **Auth / client — framework split:**
33
- - **Astro (Wix-managed):** authentication is ambient. Call `currentCart` / `productsV3` / `readOnlyVariantsV3` directly from server components and backend routes (`src/pages/api/*.ts`) — **no `createClient`, no `OAuthStrategy`, no `clientId`.**
35
+ - **Astro (Wix-managed):** authentication is ambient. Call `currentCartV2` / `productsV3` / `readOnlyVariantsV3` directly from server components and backend routes (`src/pages/api/*.ts`) — **no `createClient`, no `OAuthStrategy`, no `clientId`.**
34
36
  - **Non-Astro (Vite/React/Vue/static):** build one manual visitor client and reuse it:
35
37
  ```js
36
38
  import { createClient, OAuthStrategy } from '@wix/sdk';
37
39
  import { productsV3, readOnlyVariantsV3 } from '@wix/stores';
38
- import { currentCart } from '@wix/ecom';
40
+ import { currentCartV2 } from '@wix/ecom';
39
41
  import { redirects } from '@wix/redirects';
40
42
 
41
43
  const client = createClient({
42
- modules: { productsV3, readOnlyVariantsV3, currentCart, redirects },
44
+ modules: { productsV3, readOnlyVariantsV3, currentCartV2, redirects },
43
45
  auth: OAuthStrategy({ clientId: /* the project's PUBLIC OAuth client id */ }),
44
46
  });
45
47
  ```
@@ -49,7 +51,7 @@ A concise contract for writing the **frontend code** of a storefront against a C
49
51
 
50
52
  ## The shapes you read (field cheat-sheet)
51
53
 
52
- The exact field paths the storefront reads, and the **plausible-wrong sibling** each is mistaken for — the sections below reference these instead of re-describing them. All `amount`s are **strings**. These are **read** shapes; the cart-add body (under *Adding to cart*) is a separate **write** shape, and the `_id` rule applies to read **entities**, not to method-return wrappers (note `checkoutId`).
54
+ The exact field paths the storefront reads, and the **plausible-wrong sibling** each is mistaken for — the sections below reference these instead of re-describing them. All `amount`s are **strings**. These are **read** shapes; the cart-add body (under *Adding to cart*) is a separate **write** shape, and the `_id` rule applies to read **entities**, not to request params (note the redirect session's `checkoutId`, which is just the cart's `_id`).
53
55
 
54
56
  ```jsonc
55
57
  // productsV3.queryProducts().…find() → result.items[]
@@ -72,11 +74,12 @@ variant = {
72
74
  inventoryStatus: { inStock }, // variant-level stock (boolean)
73
75
  }
74
76
 
75
- // currentCart.getCurrentCart() → { lineItems: [...] }
76
- lineItem = { quantity, price: { amount }, image } // price is HERE (NOT actualPriceRange); image is wix:image:// too → resolve
77
+ // currentCartV2.getCurrentCart() → { cart: { _id, lineItems: [...] } } // NOTE: returns { cart } — destructure it
78
+ lineItem = { _id, name: { original }, quantityInfo: { confirmedQuantity }, pricing: { unitPrice: { amount } }, attributes: { image } }
79
+ // price → pricing.unitPrice (ConvertedMoney, NO formatted string in V2 — format it yourself; .amount is site currency, .convertedAmount the buyer's display currency); qty → quantityInfo.confirmedQuantity; image → attributes.image (wix:image:// → resolve)
77
80
 
78
- // currentCart.createCheckoutFromCurrentCart({ channelType }) → { checkoutId } // a STRING — NOT { checkout }, NOT _id
79
- // redirects.createRedirectSession({ ecomCheckout: { checkoutId }, callbacks }) → { redirectSession: { fullUrl } }
81
+ // the cart's _id is the checkout id → pass to the redirect session:
82
+ // redirects.createRedirectSession({ ecomCheckout: { checkoutId: cart._id }, callbacks }) → { redirectSession: { fullUrl } }
80
83
  ```
81
84
 
82
85
  ---
@@ -94,7 +97,7 @@ Doc: <https://dev.wix.com/docs/api-reference/business-solutions/stores/catalog-v
94
97
 
95
98
  **⚠️ CRITICAL: the entity id is `_id`, NOT `id`.** The SDK normalizes every entity's id to **`_id`**. `product.id` is `undefined` in SDK code. This is the cart-killer: feeding `product.id` into the cart's `catalogItemId` sends an empty string and the add returns **HTTP 500** (`"catalogItemId" has size 0`). Use `product._id` everywhere — in links, as the cart `catalogItemId`, and as the variant-query filter value. (If a field name surprises you, you are probably reading the REST doc view — re-open it with `?apiView=SDK`.)
96
99
 
97
- **Scope of the `_id` rule — entity reads only.** `_id` is the id of a read **entity** (product, variant, cart line item). It is **not** a universal "every id field is `_id`" rule: method results name their own fields (e.g. `createCheckoutFromCurrentCart` returns `checkoutId`, *not* `_id` — see Checkout). Don't assume a method's return wrapper exposes `_id`.
100
+ **Scope of the `_id` rule — entity reads only.** `_id` is the id of a read **entity** (product, variant, cart line item). It is **not** a universal "every id field is `_id`" rule: request params name their own fields (e.g. the redirect session takes `ecomCheckout.checkoutId`, *not* `_id` — see the *Checkout* section below — even though the value you pass is the cart's `_id`). Don't assume every id-shaped field is spelled `_id`.
98
101
 
99
102
  **Visibility:** only `visible: true` products are returned to a visitor token, so a missing product usually means it wasn't seeded visible — not a query bug.
100
103
 
@@ -149,11 +152,11 @@ Doc: <https://dev.wix.com/docs/api-reference/business-solutions/stores/catalog-v
149
152
 
150
153
  Each `variant` carries `variant.optionChoices[].optionChoiceNames` — `{ optionName, choiceName }`. Match the buyer's selected options (Size = "Small", Color = "Red", …) against those names to pick the variant. For a **single-variant** product, use the only item. Fall back to `items[0]` if matching yields nothing. The id to send to the cart is **`variant.variantId ?? variant._id`**.
151
154
 
152
- **2 · Add it.** Doc: <https://dev.wix.com/docs/api-reference/business-solutions/e-commerce/purchase-flow/cart/add-to-current-cart.md?apiView=SDK> · catalogReference contract: <https://dev.wix.com/docs/api-reference/business-solutions/stores/catalog-v3/e-commerce-integration.md?apiView=SDK>
155
+ **2 · Add it.** Doc: <https://dev.wix.com/docs/api-reference/business-solutions/e-commerce/purchase-flow/cart-v2/add-line-items-to-current-cart.md?apiView=SDK> · catalogReference contract: <https://dev.wix.com/docs/api-reference/business-solutions/stores/catalog-v3/e-commerce-integration.md?apiView=SDK>
153
156
 
154
157
  ```js
155
- await currentCart.addToCurrentCart({
156
- lineItems: [{
158
+ await currentCartV2.addLineItemsToCurrentCart({
159
+ catalogItems: [{ // the write shape uses `catalogItems`
157
160
  quantity,
158
161
  catalogReference: {
159
162
  catalogItemId: product._id, // the product's _id (the `_id` rule above)
@@ -164,28 +167,44 @@ await currentCart.addToCurrentCart({
164
167
  });
165
168
  ```
166
169
 
167
- **⚠️ CRITICAL: `options.variantId` is MANDATORY for any product that has variants.** Adding by `catalogItemId` alone returns **HTTP 200 but adds nothing** — the silent empty cart. The cart method's required-params list omits `variantId`, so this fails quietly and looks like success. Always resolve and include it (part 1 above).
170
+ **⚠️ CRITICAL: `options.variantId` is MANDATORY for any product that has variants.** Adding by `catalogItemId` alone **fails** — the catalog can't resolve a variant-bearing product without it, and Cart V2 **rejects the add with an explicit error** rather than accepting an invalid line. The cart method's required-params list omits `variantId`, so it's an easy one to miss. Always resolve and include it (part 1 above).
168
171
 
169
- **⚠️ CRITICAL: `options.options` is for MODIFIERS, not variant selection.** Product option selections (Size/Color) are resolved to a **variant** and referenced by `variantId`. `options.options` is only for free-text / TEXT_CHOICES add-on **modifiers**. Do **not** encode Size/Color as `options.options` — that is the coffee-grind bug (`200` + empty cart).
172
+ **⚠️ CRITICAL: `options.options` is for MODIFIERS, not variant selection.** Product option selections (Size/Color) are resolved to a **variant** and referenced by `variantId`. `options.options` is only for free-text / TEXT_CHOICES add-on **modifiers**. Do **not** encode Size/Color as `options.options` — that is the coffee-grind bug: the variant never resolves, so Cart V2 rejects the add with an explicit error.
170
173
 
171
- ### Checkout
174
+ ### Checkout — redirect to the hosted checkout page
172
175
 
173
- Create a checkout from the current cart, then redirect the buyer to the hosted checkout.
174
- Docs: <https://dev.wix.com/docs/api-reference/business-solutions/e-commerce/purchase-flow/cart/create-checkout-from-current-cart.md?apiView=SDK> · <https://dev.wix.com/docs/api-reference/business-solutions/e-commerce/purchase-flow/cart/get-current-cart.md?apiView=SDK>
176
+ The cart's `_id` **is** the checkout id — pass it into the redirect session's `ecomCheckout.checkoutId`. Read the current cart, then hand its id to a redirect session, which carries the visitor/member session across to the hosted checkout on its own domain.
177
+ Doc: <https://dev.wix.com/docs/api-reference/business-solutions/e-commerce/purchase-flow/cart-v2/get-current-cart.md?apiView=SDK>
175
178
 
176
179
  ```js
177
- const checkout = await currentCart.createCheckoutFromCurrentCart({ channelType: currentCart.ChannelType.WEB });
180
+ const { cart } = await currentCartV2.getCurrentCart(); // NOTE: returns { cart } — destructure it
178
181
  const session = await redirects.createRedirectSession({
179
- ecomCheckout: { checkoutId: checkout.checkoutId }, // checkout.checkoutId — NOT checkout._id
182
+ ecomCheckout: { checkoutId: cart._id }, // the cart's _id IS the checkout id
180
183
  callbacks: { postFlowUrl: `${origin}/`, thankYouPageUrl: `${origin}/` },
181
184
  });
182
185
  window.location.href = session.redirectSession.fullUrl; // the hosted-checkout URL
183
186
  ```
184
187
 
185
- **⚠️ Return shapes are in the cheat-sheet** — `createCheckoutFromCurrentCart` gives **`checkout.checkoutId`** (a string), not `checkout._id`. Reading `checkout._id` (over-applying the `_id` rule) throws *"Cannot read properties of undefined (reading '_id')"* — the silent checkout crash.
188
+ **⚠️ The cart's `_id` is the checkout id.** Pass `cart._id` straight into the redirect session's `ecomCheckout.checkoutId`. And `getCurrentCart()` returns **`{ cart }`** — destructure it, or `cart` is `undefined` and `cart._id` throws *"Cannot read properties of undefined (reading '_id')"*.
186
189
 
187
190
  **⚠️ CRITICAL: `origin` for `postFlowUrl`/`thankYouPageUrl` MUST be the `https://` published host — derive it from `window.location.origin`, NEVER `new URL(request.url).origin`.** The Headless redirect allowlist registers the site's **`https://`** host and treats **`http://<same host>` as a different, unlisted origin**. When the buyer returns from the hosted checkout (e.g. clicks "Continue Browsing"), the redirect goes through the allowlist — and an `http://` `postFlowUrl` **403s** with *"… isn't listed as an allowed redirect domain."* If you build the redirect session in a **server route** (`src/pages/api/*`), `new URL(request.url).origin` resolves to **`http://`** behind Wix's TLS-terminating proxy → guaranteed 403 on return. So **pass `window.location.origin` from the client** into the route (don't read the origin off the request), or force the scheme to `https`. Doc: <https://dev.wix.com/docs/go-headless/getting-started/setup/manage-urls/add-allowed-redirect-domains>.
188
191
 
192
+ ### Formatting cart prices
193
+
194
+ **Product** prices from `productsV3` still carry a ready-to-show `actualPriceRange.minValue.formattedAmount` — use it directly. But **Cart V2 money does not**: every cart amount — line-item `pricing.unitPrice` / `pricing.totalPrice` **and** the `estimateCurrentCart`/`calculateCurrentCart` `summary.priceSummary.*` — is a `ConvertedMoney` `{ amount, convertedAmount }` with **no** formatted string. So once items are in the cart, you format the price yourself. The currency isn't on the money object; read it from the cart (`cart.customerInfo?.currencyCode ?? cart.businessInfo?.currencyCode`), and use `convertedAmount` (buyer's display currency) when present, else `amount` (site currency):
195
+
196
+ ```js
197
+ function formatCartMoney(money, cart) {
198
+ const value = money?.convertedAmount ?? money?.amount;
199
+ const currency = cart?.customerInfo?.currencyCode ?? cart?.businessInfo?.currencyCode ?? 'USD';
200
+ return value == null ? '' : new Intl.NumberFormat(undefined, { style: 'currency', currency }).format(Number(value));
201
+ }
202
+ // e.g. line item: formatCartMoney(item.pricing.totalPrice, cart)
203
+ // subtotal: formatCartMoney(estimate.summary.priceSummary.subtotal, cart)
204
+ ```
205
+
206
+ Never hardcode `$` or assume USD — stores run in EUR/GBP too.
207
+
189
208
  ### Showing stock state
190
209
 
191
210
  Read the **V3** inventory fields: product-level in-stock is `product.inventory.availabilityStatus` (`"IN_STOCK"`); variant-level is `variant.inventoryStatus.inStock`. Reading the V1 inventory field on V3 data returns `undefined` → everything renders out-of-stock (the all-OOS bug). These come from `productsV3` / `readOnlyVariantsV3`, not the V1 module.
@@ -208,7 +227,7 @@ function imgSrc(mediaMain, w = 600, h = 600) {
208
227
 
209
228
  **Never hand-build a `static.wixstatic.com/.../v1/fit/...` URL** either — the format is easy to get wrong and the image then **403s**. Only `wix:image://` values need resolving; an already-absolute `https://` URL goes straight into `<img src>`. Doc: <https://dev.wix.com/docs/sdk/core-modules/sdk/media>
210
229
 
211
- **This applies to cart line-item images too, not just product reads.** A cart `lineItem.image` is the same `wix:image://` identifier — run it through the same `imgSrc()` helper before `<img src>`. (If you build the cart over an API route, resolve there and return a ready URL so the component never sees a `wix:image://`.)
230
+ **This applies to cart line-item images too, not just product reads.** A cart `lineItem.attributes.image` is the same `wix:image://` identifier — run it through the same `imgSrc()` helper before `<img src>`. (If you build the cart over an API route, resolve there and return a ready URL so the component never sees a `wix:image://`.)
212
231
 
213
232
  ### Rendering product descriptions
214
233
 
@@ -230,7 +249,7 @@ Optional: render a `Product` schema.org JSON-LD `<script>` from the fetched prod
230
249
 
231
250
  ## Conclusion
232
251
  A correct Catalog V3 storefront frontend:
233
- - imports **`productsV3` / `readOnlyVariantsV3` / `categories` / `currentCart` / `redirects`** — never the V1 `products`/`collections` modules;
252
+ - imports **`productsV3` / `readOnlyVariantsV3` / `categories` / `currentCartV2` / `redirects`** — never the V1 `products`/`collections` modules;
234
253
  - uses **`product._id`** (never `product.id`) as the cart's `catalogItemId`;
235
254
  - resolves the **mandatory `variantId`** via `readOnlyVariantsV3` and passes it as `options.variantId` (not `options.options`);
236
255
  - builds its category nav from a **live `categories.queryCategories()`** and filters category pages server-side with **`searchProducts` + `$matchItems: [{ id: categoryId }]`** keyed on the live `categoryId` — never a frozen seed-time `productIds` map, never `queryProducts` for category filtering, never `$hasSome`, never V1 `collectionIds`;
@@ -23,7 +23,9 @@ A contract for the **frontend code** of a pricing-plans site: showing the plans
23
23
  | List / read plans (the grid) | `@wix/pricing-plans` | `plansV3` (Plans V3 — `queryPlans`, `getPlan`) |
24
24
  | Order a plan + read a member's orders | `@wix/pricing-plans` | `orders` (`createOnlineOrder`, `memberListOrders`, `memberGetOrder`) |
25
25
  | Member login / current member | `@wix/members` + `@wix/sdk` auth | see the members recipe (`getCurrentMember`, `loggedIn()`) |
26
- | Book a service with a membership (integration) | `@wix/bookings` + `@wix/ecom` (+ `@wix/redirects`) | `bookings` (`createBooking`), ecom `checkout` (`membershipOptions`) — see *Booking with a membership* |
26
+ | Book a service with a membership (integration) | `@wix/bookings` + `@wix/ecom` (+ `@wix/redirects`) | `bookings` (`createBooking`), ecom `currentCartV2` (line-item `membershipPayment`) — see *Booking with a membership* |
27
+
28
+ > Migrating from Cart V1 / Checkout V1? The code below is V2-only — see the [migration guide](https://dev.wix.com/docs/api-reference/business-solutions/e-commerce/purchase-flow/cart-v2/migration-guide) for the before/after.
27
29
 
28
30
  **Never** import `@wix/site-pricing-plans` in headless code, and don't reach for a V1 `plans`-collection query — Plans V3 (`queryPlans` → `pricingVariants`) is the shape the seed creates.
29
31
 
@@ -98,15 +100,15 @@ This is the payoff of the seed's STEP 2: a member who holds a plan that **covers
98
100
 
99
101
  1. **Require login** — membership payment resolves against the member identity (a covered service booked anonymously just falls back to pay-per-booking).
100
102
  2. **On `createBooking`, set `selectedPaymentOption: "MEMBERSHIP"`** (instead of `ONLINE`/`OFFLINE`). Enum values: `ONLINE`, `OFFLINE`, **`MEMBERSHIP`** (pay with a pricing plan), `MEMBERSHIP_OFFLINE`. Doc: <https://dev.wix.com/docs/api-reference/business-solutions/bookings/bookings/bookings-writer-v2/create-booking.md?apiView=SDK>
101
- 3. **The booking still rides the ecom checkout** (Bookings is an ecom catalog, appId `13d21c63-b5ec-5912-8397-c3a5ddb27a97`) — the membership is applied **on the checkout**, not on the booking object. The eligible plan surfaces on the **ecom Checkout** entity's `membershipOptions`:
102
- - `membershipOptions.eligibleMemberships[]` — the member's plans that can cover the line item (each `{ _id, appId, name, credits }`).
103
- - apply one via `checkout.updateCheckout(...)` setting `membershipOptions.selectedMemberships.memberships = [{ _id, appId, lineItemIds: [<booking line item id>] }]`. When the membership covers the full price, `payNow` drops to `0`.
104
- Doc: <https://dev.wix.com/docs/api-reference/business-solutions/e-commerce/purchase-flow/checkout/checkout/get-checkout.md?apiView=SDK>
103
+ 3. **The booking still rides the ecom cart** (Bookings is an ecom catalog, appId `13d21c63-b5ec-5912-8397-c3a5ddb27a97`) — the membership is applied **on the cart line item**, not on the booking object. Set membership selection on the line item via `updateLineItemsInCurrentCart`:
104
+ - **Apply one** by setting the line item's `membershipPayment` via `currentCartV2.updateLineItemsInCurrentCart({ lineItems: [{ lineItemId: <booking line item id>, membershipPayment: { existingMembership: { membershipId, appId } } }] })` — `membershipId`/`appId` identify the member's existing plan membership (the Pricing Plans app); the line item reads it back as `paymentConfig.membership`. When the membership covers the full price, the calculated total drops to `0`.
105
+ - **Which plan to apply / eligibility:** the cart has **no** candidate-memberships read in V2 — `calculateCurrentCart` → `summary.paymentSummary.memberships` lists memberships **already applied**, not candidates — so which plan to apply comes from the member's plans + live coverage (see the LIVE-read note below). **⚠️ VERIFY IN A LIVE BUILD** which `appId`/`membershipId` a headless member token must pass.
106
+ Doc: <https://dev.wix.com/docs/api-reference/business-solutions/e-commerce/purchase-flow/cart-v2/update-line-items.md?apiView=SDK>
105
107
  4. **⚠️ Do NOT call `confirmBooking`** — for a membership payment Wix **auto-confirms** the booking on redemption (the same "no confirmBooking" rule as `how-to-code-bookings.md`; the membership drives confirmation).
106
108
 
107
- - **⚠️ Eligibility / coverage is a LIVE read, never a hardcoded plan→service map.** A new eligible plan, or a service newly added to a plan's coverage in the backoffice, **must be honored with no code change** — so never freeze the seed-time `{ planId → serviceIds }` map into the frontend. **Preferred:** read `checkout.membershipOptions.eligibleMemberships` — the plans that can cover *this* line item, computed server-side from live coverage. (It's tagged "reserved for internal use" in the schema, so **VERIFY IN A LIVE BUILD** whether a headless member token reads it client-side.) **Fallback if that field comes back empty for a member token:** list the member's active orders (`orders.memberListOrders`, `status: ACTIVE` within the date window) and match each `planId` against the plan's **live** Benefit-Program coverage (query the program's items — the same API `setup-pricing-plans.md` STEP 2 wrote to — at request time; do **not** read a coverage map baked into code). Set `selectedPaymentOption: "MEMBERSHIP"` + `selectedMemberships` from whichever read succeeds.
109
+ - **⚠️ Eligibility / coverage is a LIVE read, never a hardcoded plan→service map.** A new eligible plan, or a service newly added to a plan's coverage in the backoffice, **must be honored with no code change** — so never freeze the seed-time `{ planId → serviceIds }` map into the frontend. Since Cart V2 has no per-line "eligible memberships" read, compute eligibility from the member's own plans against live coverage: list the member's active orders (`orders.memberListOrders`, `status: ACTIVE` within the date window) and match each `planId` against the plan's **live** Benefit-Program coverage (query the program's items — the same API `setup-pricing-plans.md` STEP 2 wrote to — at request time; do **not** read a coverage map baked into code). Set `selectedPaymentOption: "MEMBERSHIP"` on the booking + the line item's `membershipPayment.existingMembership` from the matched plan.
108
110
  - **⚠️ A `MEMBERSHIP` booking on a service the plan does NOT cover fails at checkout** (unless `skipSelectedPaymentOptionValidation`, which needs elevation — don't use in a member flow). Only offer "book with membership" for services actually covered by one of the member's active plans; otherwise fall back to the normal paid booking (`how-to-code-bookings.md`).
109
- - **Credit/session plans:** for a limited (non-unlimited) plan, `eligibleMemberships[].credits.remaining` matters — `0` remaining is not eligible. An unlimited plan (seed `price:"0"`) has no credit ceiling.
111
+ - **Credit/session plans:** for a limited (non-unlimited) plan, don't pre-read a balance — apply the membership, then `calculateCurrentCart`/`estimateCurrentCart`. The summary lists the applied memberships (`summary.paymentSummary.memberships`), the covered line's `paymentConfig.membership` carries `redemptionCost` (balance deducted for this booking; `0` = unlimited) + `redemptionType` (`CREDITS`/`SESSIONS`), and an exhausted/over-limit plan surfaces **explicitly** as a `summary.violations` entry (or an error at place-order) — treat that as "not eligible." The ecom cart itself carries no remaining-balance read; the balance is owned by the plan's **Benefit-Program** domain (the same source as the coverage check above), so if you need the actual number read it there — not off the cart. An unlimited plan (seed `price:"0"`) has no ceiling.
110
112
 
111
113
  ### Out of scope (do not build these)
112
114
  - **Completing/activating payment from code** — connecting a payments account, marking orders paid, forcing an order `ACTIVE`, "activating" a subscription server-side. Payment is the member's hosted-flow step or a merchant dashboard/config task, never frontend code (see the SCOPE callout under *Subscribing*).
@@ -121,4 +123,4 @@ A correct Pricing Plans frontend:
121
123
  - imports **`@wix/pricing-plans`** (`plansV3`, `orders`) — **never** `@wix/site-pricing-plans` (its `startOnlinePurchase` is Wix-site page-code, not headless);
122
124
  - lists the grid publicly with **`plansV3.queryPlans().eq('visibility','PUBLIC')`**, reads **`plan._id`** and price from **`pricingVariants[].pricingStrategies[].flatRate.amount`** (decimal string) — never a top-level `price` — and only shows a buy button on `buyable` plans;
123
125
  - treats **subscribe and my-subscription as login-gated** (the hard members dependency), orders with **`orders.createOnlineOrder(planId)`** (no `onBehalf`, no elevate), renders free plans as `ACTIVE` immediately, and drives paid plans through a payment redirect (**exact headless redirect to be confirmed in a live build**) — and **stops at the redirect**: no payments-account connect, no order activation / mark-as-paid from code (see *Out of scope*);
124
- - for the Bookings integration, books a covered service by setting **`selectedPaymentOption: "MEMBERSHIP"`** on `createBooking`, applies the membership on the **ecom checkout** (`membershipOptions`), never calls `confirmBooking`, and falls back to matching the member's active-order `planId`s to the service's coverage when the checkout eligibility field isn't readable client-side.
126
+ - for the Bookings integration, books a covered service by setting **`selectedPaymentOption: "MEMBERSHIP"`** on `createBooking`, applies the membership on the **ecom cart line item** (`membershipPayment.existingMembership` via `updateLineItemsInCurrentCart`), never calls `confirmBooking`, and falls back to matching the member's active-order `planId`s to the service's coverage when the cart eligibility field isn't readable client-side.
@@ -26,28 +26,30 @@ A contract for the **frontend code** that lets a visitor **order** from a Wix Re
26
26
  | Menus / sections / items (display) | `@wix/restaurants` | `menus` / `sections` / `items` — see `how-to-code-restaurants.md` |
27
27
  | The ordering operation (for `operationId`) | `@wix/restaurants` | `operations` |
28
28
  | Fulfillment methods (pickup/delivery to show) | `@wix/restaurants` | `fulfillmentMethods` |
29
- | Cart (add / get / checkout) | `@wix/ecom` | `currentCart` |
29
+ | Cart (add / get / checkout) | `@wix/ecom` | `currentCartV2` |
30
30
  | Redirect to hosted checkout | `@wix/redirects` | `redirects` |
31
31
 
32
+ > Migrating from Cart V1 / Checkout V1? The code below is V2-only — see the [migration guide](https://dev.wix.com/docs/api-reference/business-solutions/e-commerce/purchase-flow/cart-v2/migration-guide) for the before/after.
33
+
32
34
  **Auth / client — framework split:**
33
- - **Astro (Wix-managed):** authentication is ambient. Call `operations` / `fulfillmentMethods` / `currentCart` directly from server components and backend routes (`src/pages/api/*.ts`) — **no `createClient`, no `OAuthStrategy`, no `clientId`.**
35
+ - **Astro (Wix-managed):** authentication is ambient. Call `operations` / `fulfillmentMethods` / `currentCartV2` directly from server components and backend routes (`src/pages/api/*.ts`) — **no `createClient`, no `OAuthStrategy`, no `clientId`.**
34
36
  ```js
35
37
  import { operations, fulfillmentMethods } from '@wix/restaurants';
36
- import { currentCart } from '@wix/ecom';
38
+ import { currentCartV2 } from '@wix/ecom';
37
39
  const { operations: ops } = await operations.listOperations();
38
40
  ```
39
41
  - **Non-Astro (Vite/React/Vue/static):** build one manual visitor client and reuse it:
40
42
  ```js
41
43
  import { createClient, OAuthStrategy } from '@wix/sdk';
42
44
  import { menus, sections, items, operations, fulfillmentMethods } from '@wix/restaurants';
43
- import { currentCart } from '@wix/ecom';
45
+ import { currentCartV2 } from '@wix/ecom';
44
46
  import { redirects } from '@wix/redirects';
45
47
 
46
48
  const client = createClient({
47
- modules: { menus, sections, items, operations, fulfillmentMethods, currentCart, redirects },
49
+ modules: { menus, sections, items, operations, fulfillmentMethods, currentCartV2, redirects },
48
50
  auth: OAuthStrategy({ clientId: /* the project's PUBLIC OAuth client id */ }),
49
51
  });
50
- // then: await client.operations.listOperations(), await client.currentCart.addToCurrentCart({...})
52
+ // then: await client.operations.listOperations(), await client.currentCartV2.addLineItemsToCurrentCart({...})
51
53
  ```
52
54
  The `clientId` is public, not a secret. A mis-wired public env var inlines as `undefined` and 400s every call.
53
55
 
@@ -55,7 +57,7 @@ A contract for the **frontend code** that lets a visitor **order** from a Wix Re
55
57
 
56
58
  ## The shapes you read (field cheat-sheet)
57
59
 
58
- These are **SDK read shapes** (`?apiView=SDK`), so entity ids are **`_id`**. Prices/fees are decimal **strings**. The cart-add body (under *Adding a menu item to the cart*) is a separate **write** shape; the `_id` rule applies to read **entities**, not to method-return wrappers (note `checkoutId`).
60
+ These are **SDK read shapes** (`?apiView=SDK`), so entity ids are **`_id`**. Prices/fees are decimal **strings**. The cart-add body (under *Adding a menu item to the cart*) is a separate **write** shape; the `_id` rule applies to read **entities**, not to request params (note the redirect session's `checkoutId`, which is just the cart's `_id`).
59
61
 
60
62
  ```jsonc
61
63
  // operations.listOperations() → { operations: [...] } (no arguments)
@@ -79,11 +81,12 @@ fulfillmentMethod = {
79
81
  // item (from items.listItems — see how-to-code-restaurants.md)
80
82
  item = { _id, name, priceInfo: { price } } // price is a decimal STRING; _id → cart catalogItemId
81
83
 
82
- // currentCart.getCurrentCart() → { lineItems: [...] }
83
- lineItem = { quantity, price: { amount }, image } // amount is the string price; image is wix:image:// → resolve
84
+ // currentCartV2.getCurrentCart() → { cart: { _id, lineItems: [...] } } // NOTE: returns { cart } — destructure it
85
+ lineItem = { _id, name: { original }, quantityInfo: { confirmedQuantity }, pricing: { unitPrice: { amount } }, attributes: { image } }
86
+ // price → pricing.unitPrice (ConvertedMoney, NO formatted string in V2 — format it yourself; .amount is site currency, .convertedAmount the buyer's display currency); qty → quantityInfo.confirmedQuantity; image → attributes.image (wix:image:// → resolve)
84
87
 
85
- // currentCart.createCheckoutFromCurrentCart({ channelType }) → { checkoutId } // a STRING — NOT { checkout }, NOT _id
86
- // redirects.createRedirectSession({ ecomCheckout: { checkoutId }, callbacks }) → { redirectSession: { fullUrl } }
88
+ // the cart's _id is the checkout id → pass to the redirect session:
89
+ // redirects.createRedirectSession({ ecomCheckout: { checkoutId: cart._id }, callbacks }) → { redirectSession: { fullUrl } }
87
90
  ```
88
91
 
89
92
  ---
@@ -126,8 +129,8 @@ Doc: <https://dev.wix.com/docs/api-reference/business-solutions/restaurants/onli
126
129
  This is the one shape that differs materially from a Stores cart. Build a line item whose `catalogReference` names the **Orders app** and carries **`operationId` + `menuId` + `sectionId`** in `options`.
127
130
 
128
131
  ```js
129
- await currentCart.addToCurrentCart({
130
- lineItems: [{
132
+ await currentCartV2.addLineItemsToCurrentCart({
133
+ catalogItems: [{ // the write shape uses `catalogItems`
131
134
  quantity,
132
135
  catalogReference: {
133
136
  catalogItemId: item._id, // the MENU ITEM's _id
@@ -141,9 +144,9 @@ await currentCart.addToCurrentCart({
141
144
  }],
142
145
  });
143
146
  ```
144
- Doc: <https://dev.wix.com/docs/api-reference/business-solutions/e-commerce/purchase-flow/cart/add-to-current-cart.md?apiView=SDK>
147
+ Doc: <https://dev.wix.com/docs/api-reference/business-solutions/e-commerce/purchase-flow/cart-v2/add-line-items-to-current-cart.md?apiView=SDK>
145
148
 
146
- **⚠️ CRITICAL: `options` MUST carry `operationId`, `menuId`, AND `sectionId` — all three.** A restaurant line item is identified by *where in the menu it was ordered from*, not by a variant. Omitting any of the three makes eCommerce unable to resolve the item against the Restaurants catalog — the add fails or the line can't be checked out. This is the restaurant analog of the store's mandatory `variantId`, and it fails the same quiet way.
149
+ **⚠️ CRITICAL: `options` MUST carry `operationId`, `menuId`, AND `sectionId` — all three.** A restaurant line item is identified by *where in the menu it was ordered from*, not by a variant. Omitting any of the three makes eCommerce unable to resolve the item against the Restaurants catalog — Cart V2 rejects the add with an explicit error rather than accepting an invalid line. This is the restaurant analog of the store's mandatory `variantId`.
147
150
 
148
151
  **⚠️ CRITICAL: there is NO `variantId` here.** `options.variantId` is a **Stores** concept — menu items have no variants in this flow. Don't copy the store recipe's `variantId` resolution; a restaurant line uses `operationId`/`menuId`/`sectionId` instead. (Menu item *modifiers* — "extra cheese" — are a separate concern and out of scope for the basic order flow.)
149
152
 
@@ -151,21 +154,21 @@ Doc: <https://dev.wix.com/docs/api-reference/business-solutions/e-commerce/purch
151
154
 
152
155
  Optional: `options.onlineOrderingPageUrl` (e.g. `"/online-ordering"`) lets the buyer click the cart line to return to the item — include it only if your site has such a page.
153
156
 
154
- ### Checkout
157
+ ### Checkout — redirect to the hosted checkout page
155
158
 
156
- Create a checkout from the current cart, then redirect the buyer to the hosted checkout (identical to the storefront flow — restaurant orders ride on the same eCommerce checkout).
157
- Docs: <https://dev.wix.com/docs/api-reference/business-solutions/e-commerce/purchase-flow/cart/create-checkout-from-current-cart.md?apiView=SDK> · <https://dev.wix.com/docs/api-reference/business-solutions/e-commerce/purchase-flow/cart/get-current-cart.md?apiView=SDK>
159
+ The cart's `_id` **is** the checkout id — pass it into the redirect session's `ecomCheckout.checkoutId`. Read the current cart, then hand its id to a redirect session (identical to the storefront flow — restaurant orders ride on the same eCommerce checkout).
160
+ Doc: <https://dev.wix.com/docs/api-reference/business-solutions/e-commerce/purchase-flow/cart-v2/get-current-cart.md?apiView=SDK>
158
161
 
159
162
  ```js
160
- const checkout = await currentCart.createCheckoutFromCurrentCart({ channelType: currentCart.ChannelType.WEB });
163
+ const { cart } = await currentCartV2.getCurrentCart(); // NOTE: returns { cart } — destructure it
161
164
  const session = await redirects.createRedirectSession({
162
- ecomCheckout: { checkoutId: checkout.checkoutId }, // checkout.checkoutId — NOT checkout._id
165
+ ecomCheckout: { checkoutId: cart._id }, // the cart's _id IS the checkout id
163
166
  callbacks: { postFlowUrl: `${origin}/`, thankYouPageUrl: `${origin}/` },
164
167
  });
165
168
  window.location.href = session.redirectSession.fullUrl; // the hosted-checkout URL (fulfillment + time slot chosen here)
166
169
  ```
167
170
 
168
- **⚠️ `createCheckoutFromCurrentCart` returns `checkout.checkoutId`** (a string), **not** `checkout._id`. Reading `checkout._id` (over-applying the `_id` rule) throws *"Cannot read properties of undefined (reading '_id')"* — the silent checkout crash. The `_id` rule is for read **entities**, not method-return wrappers.
171
+ **⚠️ The cart's `_id` is the checkout id.** Pass `cart._id` into the redirect session's `ecomCheckout.checkoutId`. And `getCurrentCart()` returns **`{ cart }`** — destructure it, or `cart` is `undefined` and `cart._id` throws *"Cannot read properties of undefined (reading '_id')"*.
169
172
 
170
173
  **⚠️ CRITICAL: `origin` for `postFlowUrl`/`thankYouPageUrl` MUST be the `https://` published host — derive it from `window.location.origin`, NEVER `new URL(request.url).origin`.** The Headless redirect allowlist registers the site's **`https://`** host and treats `http://<same host>` as a different, unlisted origin. If you build the redirect session in a **server route** (`src/pages/api/*`), `new URL(request.url).origin` resolves to `http://` behind Wix's TLS-terminating proxy → the buyer's return redirect **403s** with *"… isn't listed as an allowed redirect domain."* Pass `window.location.origin` from the client into the route (or force the scheme to `https`). Doc: <https://dev.wix.com/docs/go-headless/getting-started/setup/manage-urls/add-allowed-redirect-domains>.
171
174
 
@@ -173,16 +176,16 @@ window.location.href = session.redirectSession.fullUrl; // the hosted-checkout U
173
176
 
174
177
  ### Reading the price and the cart
175
178
 
176
- Menu item price is **`item.priceInfo.price`** — a decimal **string** with no currency symbol (see `how-to-code-restaurants.md`; the SDK `PriceInfo` type has only `price`, no `formattedPrice`). A cart line's price is **`lineItem.price.amount`** (also a string). Prefix your own currency in the UI. Cart line images are `wix:image://` identifiers — resolve them with `media.getScaledToFillImageUrl(id, w, h, {})` (4 args), never raw (see the store/menu recipes' image callout).
179
+ Menu item price is **`item.priceInfo.price`** — a decimal **string** with no currency symbol (see `how-to-code-restaurants.md`; the SDK `PriceInfo` type has only `price`, no `formattedPrice`). A cart line's price is **`lineItem.pricing.unitPrice`** / **`pricing.totalPrice`** — a `ConvertedMoney` `{ amount, convertedAmount }`; Cart V2 line items carry no preformatted price, so format it yourself. The currency lives on the cart, not the money object — format from `cart.customerInfo?.currencyCode ?? cart.businessInfo?.currencyCode` (see the *Formatting cart prices* section of `how-to-code-a-store.md` for the exact `Intl.NumberFormat` helper), e.g. `new Intl.NumberFormat(undefined, { style: 'currency', currency }).format(Number(pricing.totalPrice.convertedAmount ?? pricing.totalPrice.amount))`. Cart line images are `wix:image://` identifiers — resolve them with `media.getScaledToFillImageUrl(id, w, h, {})` (4 args), never raw (see the store/menu recipes' image callout).
177
180
 
178
181
  ---
179
182
 
180
183
  ## Conclusion
181
184
  A correct Restaurants ordering frontend:
182
- - imports **`operations` / `fulfillmentMethods` from `@wix/restaurants`** (plus `menus`/`sections`/`items` for display) and **`currentCart` / `redirects`** from `@wix/ecom` / `@wix/redirects`;
185
+ - imports **`operations` / `fulfillmentMethods` from `@wix/restaurants`** (plus `menus`/`sections`/`items` for display) and **`currentCartV2` / `redirects`** from `@wix/ecom` / `@wix/redirects`;
183
186
  - uses **`_id`** (never `id`) for the operation, menu, section, and item;
184
187
  - reads the **`operationId`** once from `listOperations()` (the `ENABLED`/`default` operation) and reuses it;
185
188
  - adds menu items with the **Orders app id `9a5d83fd-…`** (never the Stores id) and `options` carrying **`operationId` + `menuId` + `sectionId`** (all three) — **no `variantId`**;
186
189
  - threads `menuId`/`sectionId` from the render context, not a re-lookup;
187
- - checks out via **`createCheckoutFromCurrentCart` → `checkout.checkoutId` → `redirects`**, with an **`https://` `window.location.origin`** for the callbacks; the hosted checkout collects fulfillment + payment (which needs a premium plan + payment method).
190
+ - checks out by feeding the **current cart's `_id`** (the checkout id) to **`redirects`**, with an **`https://` `window.location.origin`** for the callbacks; the hosted checkout collects fulfillment + payment (which needs a premium plan + payment method).
188
191
  </content>
@@ -4,6 +4,11 @@ For a **managed** project (hosted on Wix infrastructure), every Wix call uses a
4
4
 
5
5
  ## 1 · Ensure an authenticated CLI session
6
6
 
7
+ ### Secret boundary — mandatory
8
+
9
+ Never read authentication material or tokens into agent context.
10
+ Never emit them to stdout, stderr, logs, prompts, or tool results.
11
+
7
12
  ```bash
8
13
  npx @wix/cli@latest whoami # exits 0 when logged in; non-zero when logged out
9
14
  ```
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: wix-manage
3
- description: "Wix business solution management recipes — REST API operations for configuring and managing Wix business solutions. Routes to: stores, bookings, get-paid, CMS, contacts, forms, media, app-installation, pricing-plans, restaurants, ricos rich-content, sites, blog, calendar, domains, events, site-properties, ecommerce, marketing, google-ads, analytics, accessibility, dashboard-navigation."
3
+ description: "Wix business solution management recipes — REST API operations for configuring and managing Wix business solutions. Routes to: stores, bookings, get-paid, CMS, contacts, forms, media, app-installation, pricing-plans, restaurants, ricos rich-content, sites, blog, calendar, domains, events, site-properties, ecommerce, marketing, google-ads, analytics, accessibility, seo, dashboard-navigation."
4
4
  compatibility: Requires Wix REST API access (API key or OAuth).
5
5
  ---
6
6
 
@@ -34,6 +34,16 @@ These recipes do NOT cover frontend development or SDK usage for displaying data
34
34
 
35
35
  ---
36
36
 
37
+ ## SEO
38
+
39
+ ### [Manage a Wix Site's SEO Tags](references/seo/manage-seo-tags.md)
40
+ **Technical:** Reads and writes SEO tags (titles, descriptions, social tags, structured data, indexing) at three levels: site-wide, page-type patterns, and individual items. Always read before write — every Set replaces in full.
41
+
42
+ ### [Manage URL Redirects on a Wix Site](references/seo/manage-url-redirects.md)
43
+ **Technical:** Reads, creates, and deletes URL redirects through the public SEO Redirects API. Distinguishes exact from group redirects, handles language-scoped redirects on multilingual sites, reads per-item results from bulk responses, and confirms before writes, because creating a redirect can permanently delete an existing one.
44
+
45
+ ---
46
+
37
47
  ## Accessibility
38
48
 
39
49
  ### [Scan a Wix Site for Accessibility Issues](references/accessibility/scan-site-accessibility.md)