Upload a Website or HTML Files

Download skillThe skill is a reference md and part of wix-manage skill. You can use the following command to add the full wix-manage skill to your project:
Copy

The user has a finished website as files — hand-written HTML, a static build, a zip, or the output of an AI site builder — and wants it live on Wix as a static, Wix-hosted site.

Choose the route

Five ways to get the files live; what you have decides which are open to you.

OptionNeedsCarriesThe user ends up with
A. curl + CLI token → into the accountA shell; a Wix CLI loginAnything on diskA site in their account, final URL
B. ExecuteWixAPI → into the accountThe Wix MCPFiles already in the conversation — text as is, small binaries as base64 — and anything downloaded from the siteA site in their account, final URL
C. curl → anonymousA shellAnything on diskA live site for one hour; kept by a claim (through the Wix MCP or a CLI token) or the save link
D. The drop pageNothingWhatever the user uploadsThe same, after they upload it themselves
E. The headless skillA shell; Node; a Wix CLI loginA project folder, source included (built for you)A site in their account as a Wix Headless project, released with the Wix CLI, ready for Wix Business Solutions

What sets them apart:

  • curl -F streams files from disk — the bytes never pass through you: any type, any number, up to the limits.
  • ExecuteWixAPI has no filesystem. The Wix MCP's tool runs JavaScript whose wix.request calls carry the user's login — no install, no token — but every byte is written out inside the call. That costs nothing extra for a page you generated or the user pasted (it's already in the conversation); for files on disk it means reading them in and writing them back out, and each change resends all of it. Files travel in the tool's files param — text raw, a binary file (PNG, JPG, fonts) as base64 — and wix.multipart() builds the upload body (below). Base64 costs about a third more than the file and every byte is tokens, so it suits small assets (an icon, a logo, a font) and files you downloaded from the site to change; a photo goes in by absolute URL (<img src="https://…">) or with curl from a shell. The bundle is capped at 4M characters.
  • A CLI login is one approval by the user in the browser: run npx @wix/cli login and have them approve; npx @wix/cli token then prints a token (see Before the calls). It also unlocks later changes from disk and Keep building.
  • Anonymous needs no identity, but the record expires after an hour and the URL changes on claim.
  • The headless skill is the heaviest — an install, a project, a build — and the only one that takes framework source as is and leaves a project ready for a backend. A drop site can still move to it later.

So: a site already in the conversation — pages, styles, a logo — with the Wix MCP connected — B, shell or not. Static files on disk — A with a CLI login, else C (claimed through the Wix MCP when it's connected). A framework project (a package.json), or a site that needs stores, bookings, a CMS or members from the start — E. Files out of your reach, or nothing above fits — D.

Publishing yourself beats the drop page whenever an option fits — the user gets a live site without uploading anything. Never report an upload you couldn't perform; whenever a route fails partway, hand over the drop page.

Before the calls

The curl examples here and in the anonymous route read shell variables.

  • $ACCESS_TOKEN is the user's access token, from wherever you have it. A token from the Wix CLI (npx @wix/cli token) lasts 15 minutes; running the command again returns a valid one, refreshed if needed, so set $ACCESS_TOKEN again when it may have expired, and always after a 403.
  • $META_SITE_ID, $UPLOAD_ID, … come from the previous response; set each one before the next call.

In an ExecuteWixAPI script there is no token to handle, and the ids go into the URL as values; a $NAME there is sent as is.

Publish into the user's account

1. Create the site

The Create Headless Site call with no Wix Business Solutions. Name it after the page's <title>, and keep "origin": "drop": it marks the site as a dropped one, the same as the drop page and the anonymous route do.

Copy
Copy

2. Drop the files — the site goes live

One multipart request uploads and releases. Each file is a part named files whose filename is its path relative to the site root — that is how subdirectories survive; with curl, set it with ;filename= whenever it isn't just the basename. A single .zip part works too — it's unpacked server-side, a single wrapping folder stripped.

Copy
Copy

Option B — the same two calls as one ExecuteWixAPI script, for files already in the conversation. The files go in the tool's files param, not in code: one bundle, each file introduced by a line === FILE: <path> === and followed by its raw text, <path> relative to the site root. A binary file is introduced by === FILE: <path> base64 === and followed by its base64. Nothing in it is escaped, so quotes, backticks, ${} and backslashes arrive as written.

Copy

In code, the bundle is the files global ([{ path, content, encoding? }], encoding: 'base64' on the binary entries) and wix.multipart() turns it into the upload body: one part named files per file, the path as its filename, a content type guessed from the extension, base64 entries decoded to their bytes.

Copy

wix.request returns { status, data }; read a response's fields from data (created.data.metaSiteId). Don't build the multipart body by hand — a missing \r\n or a boundary mismatch is a bare 500 — and don't put file text inside code as string literals: that second layer of escaping is what corrupts backslashes and ${}. Don't put image bytes in code either — they're a base64 entry in files. The one thing files cannot carry is a file with a line that reads exactly === FILE: … ===.

siteUrl is the site's final address — it's already in the user's account. Give the user two links: siteUrl, and its dashboard at https://manage.wix.com/dashboard/{metaSiteId}.

Change it later

Re-run step 2 on the same metaSiteId with the full file set: each drop replaces the site's files (a file left out is gone), and siteUrl stays the same. Never create another site for a change. The same call updates any site the user owns that was published this way, including one claimed from an anonymous publish. For a site from an earlier conversation, find its metaSiteId with the Query Sites call below, matching the site's name or viewUrl.

When you no longer have the files (a small change to a site from an earlier conversation), download what the site serves, edit it, and drop the full set back. Leave out wix.config.json; the download adds it, and it isn't part of the site.

Copy

In an ExecuteWixAPI script, request the same URL with responseType: 'base64' and unzip it in memory with wix.unzip(). Each entry is { path, bytes, text() }: bytes is the file as stored, text() decodes it when it's text. Don't decode every entry — an image run through a text decoder is corrupted.

Copy

Edit in memory — text through text(), binaries left as they are — then drop the full set back in the same script, leaving wix.config.json out. Entries go to wix.multipart() as they are; an edited file replaces its entry with { path, content }:

Copy

The live URL itself (*.wix-site-host.com) can't be read from a script; this download is the way back to the files. Don't rebuild the site from memory.

Publish anonymously

No identity needed: the site is created under a temporary owner and lives for one hour unless the user keeps it.

Generate anonymousId yourself — any UUID, once per site, not once per request — and reuse it, with the returned metaSiteId, for every call for that site. Finish within one hour of step 1: after that the record expires and every later call, claim included, returns 404.

1. Create the site

Copy
Copy

Keep both: metaSiteId addresses the site, projectId builds the save link.

2. Upload the files

Same multipart shape as the drop, to the upload path, with the attribution parameters (in a script: wix.multipart() with the files param, posted to this URL):

Copy
Copy

Keep campaign=mcp (the referral tag — don't change it) and set agent to your own identifier: a short, stable, lowercase-hyphenated slug for the coding agent or tool you are (e.g. claude-code, cursor, codex-cli, windsurf, github-copilot, or your product's name; unknown-agent if you can't name yourself). Nothing is live yet; this only stages and validates.

3. Release — the site goes live

Copy
Copy

To change it, re-run steps 2–3 with the same anonymousId and metaSiteId and the full file set; siteUrl stays the same. Never go back to step 1 for a change. Iterate first, claim last: after a claim, changes are a drop, which needs the user's identity — a CLI token for files on disk.

When it's final: with the user's identity, claim it. Without it, stop here — a finished result. Give the user siteUrl plus the save link, which is how the site survives:

Copy

That page shows the site with a countdown and signs the user in to keep it. Treat the link as a secret — whoever opens it while signed in to Wix claims the site into their account — so give it only to the user who asked.

Claim it into the user's account

Copy

Returns {}. Claim after the release, never before — it consumes the anonymous record, so the anonymous endpoints stop working for this site; later changes go through the drop.

The URL changes on claim: the step-3 host stops resolving. Read the new one with Query Sites, filtered to the HEADLESS namespace (the default query omits headless sites, and an id filter is rejected, so match the id yourself):

Copy

Take viewUrl from the entry whose id is your metaSiteId (page with metadata.cursors.next if needed), and give the user it plus https://manage.wix.com/dashboard/{metaSiteId}.

The drop page

Copy

Keep utm_campaign=mcp and set agent to your own identifier, as in the upload. The user drags in their files (no login), Wix hosts them on a live URL at once, and a banner offers to sign in and keep the site. Tell them the requirements so it doesn't fail on the first try.

What the upload accepts, and how it fails

These apply to every route:

  • A top-level HTML file is required. A lone top-level HTML of any name becomes the homepage; with more than one, an index.html must be among them.
  • 3 MB per file, 20 MB per site.
  • Static files only — HTML, CSS, JS, images, fonts. Framework source that needs a build step (a package.json, React/Vue sources) must be built first; upload the build output, or take the project to the headless skill (option E).

Failures come back as HTTP 400 with a code in details.applicationError.code: MISSING_INDEX_HTML, FILE_TOO_LARGE, TOTAL_TOO_LARGE. Beyond those:

  • 403 PERMISSION_DENIED on a drop — the token expired or is missing (see Before the calls), or the site isn't the caller's. A site that's still anonymous is changed by upload + release, not by a drop.
  • 500 on a drop or upload — the multipart body was built by hand and is malformed (see the shape above). Rebuild it from the example; sending it again unchanged fails the same way.
  • 404 on the anonymous route after step 1 — the hour passed or the site was claimed; start again.

If publishing fails for any reason you can't quickly fix, hand the user the drop page. Never leave them with a failed publish and no way forward.

Keep building: add a backend when you need one

The headless skill, https://wix.com/headless/skill.md, builds and releases a Wix Headless project with the Wix CLI: it adopts a project folder (a package.json, or an index.html at its root) into a new site, or takes a dropped one. A dropped site is static; when it needs a real backend — stores, payments, bookings, a CMS, members, forms — it moves to a headless project, keeping the same site, appId and URL. This is a choice the user makes when the need appears; static changes never need it, they're a drop.

To move a dropped site, in a shell, once it's in the user's account:

Copy

Then follow the headless skill from that folder: it turns the files into a headless project bound to the same site, released with the Wix CLI from then on.

Route the request correctly

  • A new site from the user's files — Choose the route.
  • A change to a site published this way — the same site, full file set: a drop when it's in the user's account, upload + release while it's anonymous.
  • An anonymous site the user wants to keep — claim it, else the save link.
  • A site that now needs a backend — Keep building.
  • Migrating a live site/store from another platform by URL, or CSV/TSV exports — Site Import.
  • Adding HTML, an embed, or code to an existing Wix site — not this recipe (that's custom code in the editor).
  • Images, videos, or documents for a site — Upload Media to Wix.

Don't create the site from a template — that makes an empty site, not a published copy of the user's files.

Last updated: 6 October 2026

Did this help?