All skills
wix avatar

/wix-headless

@8f7fa5d official
by Wix.comwix/skills33 stars
33

Connect Wix business services (Stores, Bookings, CMS, Blog, Events, Forms, and more) to a Wix Headless frontend — infer the needed capabilities, install the apps, seed backend content, and produce an SDK-integration guide. For managed (Wix-hosted) projects it can also build the frontend: scaffold a new site (create) or wire an existing/brought-in design (connect), then build and release. Works across managed, self-managed, and stripe project types. Triggers: set up a Wix Headless backend, add Wix business features to my app, build or host a Wix site, connect/implement this design with Wix.

Use this Skill: https://skilld.dev/gh/wix/skills/wix-headless

This session only. Nothing lands on disk.

referencesinline-recipessetup-online-store.md

≈4.3k tokens on demand. Your agent reads this file only when SKILL.md points to it.

RECIPE: Business Recipe – Initial Setup for a Wix Online Store (Catalog V3)

Standard call shape (every curl below). The <AUTH> placeholder is shorthand for Authorization: Bearer <TOKEN> only. Body-bearing requests also need Content-Type: application/json.

A concise checklist for preparing any new Wix site that uses the Online Stores app with Catalog V3. Notice that this recipe is NOT meant for coding purposes and is ONLY meant for initial catalog setup.

This recipe is the how, not the what. What to seed — how many products, which are in/out of stock, which categories — is determined by the request you're fulfilling. This recipe only specifies the calls and the request format; it does not decide quantities or which items to create.

API surfaces: products use Catalog V3 (https://www.wixapis.com/stores/v3/...). The Categories API is the exception — it lives on a v1 endpoint (https://www.wixapis.com/categories/v1/categories). Don't mix in any pre-V3 product endpoints.


Article: Steps for Setting Up a Wix Online Store

YOU MUST complete the following steps in the given order (1-4) and without requiring additional user input. One conditional: the category steps (3-4) run only when the request names categories — if intent.stores.categoriesNamed is empty, create none (skill policy, per SEED.md § "What to seed per capability") and skip the category steps. Products (steps 1-2) always run; the Attach images step runs last, only when imagery is on.

⚠️ CRITICAL ORDER REQUIREMENT: Do the product operations FIRST (clean + create, Steps 1-2), then categories (Steps 3-4). Categories API might take some time to be fully available after Stores installation, so always finish products before attempting category operations.

STEP 1: Clean the store — remove the default sample products

A freshly provisioned Wix Stores app comes pre-seeded with demo/sample products. Only remove products that are obviously the install's own demo/sample data on a fresh install. Do not assume the existing products are samples: the site may already hold the owner's real catalog (a connect/iterate run, or an owner-populated store). If what's there isn't obviously install demo data, or you're unsure, do not delete it — ask the user first (SEED.md: seeding is additive; deleting real content needs the owner's approval). When they clearly are the install's samples, remove them before creating yours so the storefront shows only the intended catalog.

  1. List the existing products — POST https://www.wixapis.com/stores/v3/products/query with body {"query": {"paging": {"limit": 50}}}. Collect every product.id from the response.
  2. Bulk-delete them in one call — POST https://www.wixapis.com/stores/v3/bulk/products/delete with body {"productIds": ["<id1>", "<id2>", …]} (the ids from step 1; up to 100 per call). On a fresh install the query returns only the install's sample products — delete those. If it returns anything that could be the owner's real catalog, stop and ask first (above).

STEP 2: Bulk-create the products (with options)

Create the products in a single bulk request to POST https://www.wixapis.com/stores/v3/bulk/products-with-inventory/create. How many products, and which are in or out of stock, are set by the request you're fulfilling — this step only gives the call and the required format. Each product needs a real web image URL relevant to it, and price via actualPrice (+ optional compareAtPrice).

First decide each product's type by what the buyer receives: shipped → PHYSICAL (the JSON below); a file the buyer downloads and keeps → DIGITAL (the digital block below — carries a file, no quantity). Access — a membership or an online course/program the buyer enrolls in — is Pricing Plans, not a Stores product.

⚠️ CRITICAL: PRODUCT & VARIANT VISIBILITY — set "visible": true explicitly. Storefront product queries (searchProducts / queryProducts) return only visible products to site visitors. A product created without "visible": true is created successfully but will NOT appear on the live site — the catalog looks empty. So:

  • Set "visible": true on every product (product level).
  • Set "visible": true on every variant. A variant with inventoryItem.quantity > 0 is buyable; a variant that should be out-of-stock stays "visible": true with "quantity": 0.
  • Never rely on a default — always include visible.

Request body shape (one representative product shown — repeat product objects inside the products array):

{
  "products": [
    {
      "name": "Pro Basketball Sneaker",
      "productType": "PHYSICAL",
      "physicalProperties": {},
      "visible": true,
      "visibleInPos": true,
      "description": {
        "nodes": [
          {
            "type": "PARAGRAPH",
            "id": "desc1",
            "nodes": [{ "type": "TEXT", "textData": { "text": "High-performance basketball sneaker with superior ankle support." } }],
            "paragraphData": { "textStyle": { "textAlignment": "AUTO" } }
          }
        ],
        "metadata": { "version": 1, "id": "basketball-desc-001" }
      },
      "options": [
        {
          "name": "Size",
          "optionRenderType": "TEXT_CHOICES",
          "choicesSettings": { "choices": [ { "choiceType": "CHOICE_TEXT", "name": "8" }, { "choiceType": "CHOICE_TEXT", "name": "9" } ] }
        },
        {
          "name": "Color",
          "optionRenderType": "SWATCH_CHOICES",
          "choicesSettings": { "choices": [ { "choiceType": "ONE_COLOR", "name": "Red", "colorCode": "#FF0000" }, { "choiceType": "ONE_COLOR", "name": "Blue", "colorCode": "#0000FF" } ] }
        }
      ],
      "variantsInfo": {
        "variants": [
          {
            "choices": [
              { "optionChoiceNames": { "optionName": "Size", "choiceName": "8", "renderType": "TEXT_CHOICES" } },
              { "optionChoiceNames": { "optionName": "Color", "choiceName": "Red", "renderType": "SWATCH_CHOICES" } }
            ],
            "price": { "actualPrice": { "amount": "159.99" }, "compareAtPrice": { "amount": "200.00" } },
            "visible": true,
            "inventoryItem": { "quantity": 25, "preorderInfo": { "enabled": false } },
            "physicalProperties": {}
          }
        ]
      }
    }
  ],
  "returnEntity": true
}

⚠️ CRITICAL FORMAT REQUIREMENTS:

  • Description MUST be rich-text nodes, not a plain string — a plain string causes an "Expected an object" error. Use the { "nodes": [...], "metadata": {...} } shape shown.

  • Media — gated by the imagery policy (SEED.md § "Entity images"), no exception for stores. Always create products text-only here — omit media. When imagery is on, the Attach images step below writes generated brand images in a second pass; it does not happen at create time.

  • Choose the product type from what the buyer receives: a shipped item is PHYSICAL (default); a downloadable file they keep — ebook, PDF, template, preset pack, track, downloadable video — is DIGITAL. Selling access rather than a file — a membership, subscription, or an online course/program the buyer enrolls in — is Pricing Plans, not a Stores product.

  • Physical products MUST set "productType": "PHYSICAL" and an empty "physicalProperties": {} (on the product and on each variant).

  • Digital products ("productType": "DIGITAL") drop physicalProperties and are sellable only when each variant carries both a file and stock — miss either and the product is created fine, reads back visible: true, and is rejected at add-to-cart. Upload the file first (POST /site-media/v1/files/generate-upload-url → PUT the bytes → file.id):

    { "productType": "DIGITAL", "visible": true,
      "variantsInfo": { "variants": [ {
        "price": { "actualPrice": { "amount": "12.00" } }, "visible": true,
        "inventoryItem": { "inStock": true },
        "digitalProperties": { "digitalFile": { "id": "<file.id>" } }
      } ] } }
  • Options: text options use "optionRenderType": "TEXT_CHOICES" + "choiceType": "CHOICE_TEXT"; color options use "optionRenderType": "SWATCH_CHOICES" + "choiceType": "ONE_COLOR" + a colorCode.

  • Variants = the full Cartesian product of all option choices; each variant references all options via optionChoiceNames, sets price.actualPrice.amount (+ optional compareAtPrice.amount) as strings, and inventoryItem.quantity.

  • If part of the bulk request fails, retry the failed products once with the exact same format; do not loop.

⚠️ CRITICAL: options/variants are for things the buyer selects and buys — not for attributes you only filter or display by. Make something an option (and thus a variant) only if the buyer picks it to purchase a distinct SKU (Size, Color, Format). An attribute you merely filter, badge, or display by — roast level, material, genre, "new arrival" — is not a variant: encode it in the product name, its category, or description, and never as an option. Modeling a display-only attribute as an option multiplies the variant Cartesian product for nothing (slower seeding, larger payload) and produces a buyer-facing selector that shouldn't exist. (A single-variant product is fine — give it one variant with no options.) Default cardinality — keep it small: unless the request names specific options, give each product at most one option with ≤3 choices (≤3 variants); many products legitimately have none (a single variant). This is a floor, not a cap — honor a larger/explicit option set when intent names one. The two-option example above shows the multi-option format; it is not the default shape to reach for.

⚠️ Reading the response — created products are under productResults.results[], NOT a top-level results. A successful bulk create returns 200 with this shape:

{ "productResults": { "results": [
  { "itemMetadata": { "id": "<productId>", "originalIndex": 0, "success": true },
    "item": { "id": "<productId>", "slug": "<slug>", "name": "…", "visible": true, … } }
] } }

Read each product's id (→ the catalogItemId you'll assign to categories in STEP 4 and the frontend will use) and slug from productResults.results[].item. There is no top-level results key — reading response.results finds nothing and makes a successful create look like it returned zero products. Check productResults.results first; do not re-create on an empty top-level results.

STEP 3: Create the store's categories

Create the categories for the store — the categories that fit the user's request. Which categories (and how many) are set by the request you're fulfilling; this step only gives the call and format.

Use the Create Category endpoint: POST https://www.wixapis.com/categories/v1/categories. There is no bulk-create endpoint for /categories/v1/ (the events/v1/bulk/categories/create URL is for the Events product, not Stores).

⚠️ CRITICAL: create the categories SEQUENTIALLY — one call at a time, never concurrently. Every category lives in the single shared @wix/stores category tree, which carries one revision. Firing the creates concurrently makes them race on that revision: all but one fail with 409 INVALID_REVISION ("Outdated revision for entity id @wix/stores"), and the failed-then-retried calls can leave duplicate categories behind. So issue each create, wait for its response, then issue the next. (There are only a handful of categories — the serial cost is negligible.)

The request body must include a top-level treeReference field — it must not be nested inside the category object:

{
  "category": {
    "name": "Drinkware",
    "description": "The Drinkware category includes a wide range of containers designed for holding beverages",
    "visible": true
  },
  "treeReference": {
    "appNamespace": "@wix/stores",
    "treeKey": null
  }
}

STEP 4: Add Each Product to a Category

Add each product to the category it belongs to (per the request you're fulfilling). Acquire the product ids (from STEP 2's response) first. Use the Bulk Add Items To Category endpoint — products are the items: POST https://www.wixapis.com/categories/v1/bulk/categories/{categoryId}/add-items (one call per categoryId — the path parameter is single).

Issue these add-items calls SEQUENTIALLY too — one call at a time, waiting for each response before the next. They mutate the same shared @wix/stores tree as the creates above, so concurrent calls risk the same 409 INVALID_REVISION race. Serial is the safe, deterministic choice (the category count is small).

The request body is items (each with the product's catalogItemId and the Stores appId) plus a treeReference at the same level as items (not nested):

{
  "items": [
    { "catalogItemId": "<productId>", "appId": "215238eb-22a5-4c36-9e7b-e7c08025e04e" }
  ],
  "treeReference": {
    "appNamespace": "@wix/stores",
    "treeKey": null
  }
}

Attach images (imagery ON only — skip otherwise)

Only when imagery is on (SEED.md § "Entity images"). Products were created text-only above; this pass-2 step writes a generated brand image onto each. Generate + import per references/IMAGE_GENERATION.md, keep each image's url.

Attach every product in ONE call — POST https://www.wixapis.com/stores/v3/bulk/products/update (up to 100 products). You already hold each product's id and revision from STEP 2's bulk-create response (productResults.results[].item), so no per-product GET is needed. Each entry nests under a product wrapper carrying just id, revision, and media — options, variants, and every other field are left untouched (it's a partial update):

{
  "products": [
    { "product": {
        "id": "<product id>",
        "revision": "<from the create response>",
        "media": { "itemsInfo": { "items": [ { "url": "<static.wixstatic.com/… or Wix Media id>", "altText": "…" } ] } }
    } }
  ]
}
  • revision is required on every entry — omitting it fails 400 "revision must not be empty". Use the revision from the create response (or a fresh query); a stale one fails the optimistic-concurrency check.

  • Everything nests under product — id / revision / media at the entry root fails validation. Send no field mask (the validator runs before masking → 428).

  • Write the image via media.itemsInfo.items; the server derives media.main from it. One primary image per product (a larger gallery is out of scope for the seed). The image may be referenced by a static.wixstatic.com url or by its Wix Media id (<hash>~mv2.png) — either works.

  • Success shape — bulkActionMetadata totals the run, and each result carries an itemMetadata:

    {
      "results": [ { "itemMetadata": { "id": "<product id>", "originalIndex": 0, "success": true } } ],
      "bulkActionMetadata": { "totalSuccesses": 1, "totalFailures": 0, "undetailedFailures": 0 }
    }

    Read itemMetadata.success per product (the result carries no product body). Retry only the failed ids once, then move on.

  • Confirm by requery — POST …/stores/v3/products/query with "fields": ["MEDIA_ITEMS_INFO"]; the attached image is at media.main.image.url. Wix re-hosts the image on attach, so that URL is a new wixstatic.com id — verify it's a wixstatic URL, never string-equality against the one you sent. The read-back can lag a beat; one empty read right after success isn't a failure.

  • Never block on image failure — a product that won't attach stays text-only.

One-off fix (single product). To (re)attach a single image later, PATCH https://www.wixapis.com/stores/v3/products/{id} with the same { "product": { "id", "revision", "media": {…} } } body — same rules apply (revision required, no field mask, media-only is a partial update that leaves options/variants intact). Read the current revision from GET …/products/{id} → response.product.revision (the GET is not flat); the 200 response carries the image at media.main.url.


Conclusion

Following these steps in order sets up a new V3 Wix Online Store site:

  • Starts from a clean catalog — the install's default sample products are removed first.
  • Contains the products and categories called for by the request, all created visible: true so they appear on the live site.
  • Each product is connected to the category it belongs to.
  • All products follow the correct Catalog V3 API format specified in STEP 2.

Source: SKILL.md on GitHub

1 warning1d3 checks · Risk SAFE
  • Gen Agent Trust Hub1d

    The wix-headless skill is a robust tool for integrating Wix services into headless frontends. It incorporates secure practices for credential management, ensuring tokens remain outside the AI context. While the skill performs remote code execution and has data ingestion surfaces, these actions are carried out through official vendor channels and structured workflows.

  • Socket1d

    1 alert: gptAnomaly

  • Snyk1d

    Risk: LOW · No issues

Signed by skilld at 8f7fa5d. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub yesterday.

Activeupdated 2 days ago
What it can do
Runs commands Reads files Edits files
All 16 allowed tools
Bash(curl *)Bash(npx @wix/cli@latest *)Bash(npx @wix/cli *)Bash(npm create @wix/new@latest *)Bash(npm install *)Bash(npm run *)Bash(node *)Bash(cd *)Bash(ls *)Bash(mkdir *)Bash(cp *)Bash(mv *)Bash(uuidgen)ReadWriteEdit
  • wix
  • headless
  • ecommerce
  • astro
  • vite
  • jsx
  • hosting
  • cms
  • bookings

README badge

README badge for wix/skills/wix-headless

Scaffolds a new Wix Headless site from scratch or connects an existing HTML/JSX/Vite project to Wix Headless for hosting and business features (ecommerce, bookings, forms, CMS). Handles discovery, design, feature wiring via the Wix SDK, and deployment; routes on operation type (create vs. connect) and frontend framework (Astro by default, or your own build).

Generated from the current SKILL.md.

Does this skill create a new site or connect an existing one?
Both. Path A creates a new site from scratch based on your description (e.g. 'build me an online store'). Path B connects an existing project (HTML/JSX/Vite app, Claude Design output, etc.) to Wix Headless for hosting and feature wiring.
What happens when I connect an existing project to Wix Headless?
The skill analyzes your project, installs the necessary Wix Business Solutions apps, and wires the Wix SDK directly into your existing source files so each installed app powers its corresponding feature.
What frameworks does this skill support?
For new sites, it defaults to Astro but supports any framework you explicitly name (React, Vue, Svelte, Vite, etc.). For existing projects, it works with HTML, JSX, TSX, Vue, and other web frontends.
Does this skill handle ecommerce, bookings, and content sites?
Yes. It routes to different vertical packs depending on your needs: stores and ecommerce, bookings and appointments, CMS and blogs, forms, and gift cards. All verticals include CMS by default.
How does the skill authenticate with Wix?
It uses the Wix CLI (`npx @wix/cli@latest token`) to generate bearer tokens for API calls. The login flow is safe for non-interactive agents and includes a recovery ladder if needed.

Generated from the current SKILL.md. These answers refresh after source changes.