Wix serves uploaded static files at a *.wix-site-host.com URL. A raw drop
replaces the entire file set: omitted files disappear. The file helpers below
simulate a patch by downloading the current ZIP, merging changes, and dropping
the complete result. They preserve omitted files, but still transfer the full
site and are not atomic against another editor.
| Request | Flow |
|---|---|
| Publish files as a new site | Choose an upload route, create once, upload, verify. |
| Publish a Wix Astro build | Create once for a new site, then release it; it is not dropped. |
| Inspect or edit a deployed site | Find its ID, then list, read or patch. Do not create another site. |
| Replace the entire deployed frontend | Confirm replacement of the serving frontend, then drop the complete intended file set on the same ID. |
| Make a shop, bookings, forms or other business controls work | Connect a backend on the same site. A static upload alone does not make them functional. |
| Publish without a login | Anonymous upload and release, then claim or give the private save link. |
| Migrate a live site by URL or CSV/TSV | Site Import, not this recipe. |
| Add an embed to an editor-built site / upload media assets | Use the editor / Upload Media to Wix, respectively. |
A request to publish or edit authorizes that work; do not ask again. A request to inspect files authorizes no writes. Do not create from a template: that creates an empty site rather than publishing the supplied files.
Quick reference: upload through MCP · upload with curl · release an Astro build · list/read · patch · ZIP fallback · images · limits/errors.
Choose an already-authenticated route that can access the files, not by the client's name. Check the available tools before choosing:
UploadHeadlessWebsiteFiles for a complete
upload and ExecuteWixAPI for API scripts and list/read/patch helpers.
ExecuteWixAPI supplies authenticated wix.request({ scope: 'account', ... }),
returning { status, data }; no token or extra CLI login is needed.curl -sI https://www.wixapis.com for an HTTP
status. Run npx @wix/cli@latest login once for browser approval, then
npx @wix/cli@latest token. Its token lasts 15 minutes; refresh after expiry
or a 403. Use it as $ACCESS_TOKEN. Without login, use the anonymous route.Network access does not give access to files on the user's machine. If you cannot read their files, ask for an attachment/export or give the drop page; do not provision an empty site.
Build projects that need dependencies first, and upload only their static output. If your environment cannot build the project, ask for the build output. For a Wix business project, follow Connect a backend.
| What you have | How it goes live |
|---|---|
| Static pages: HTML, CSS, JS, images, or a static build's output | Dropped, by every route in this recipe. |
A Wix Astro build: .wix/build-metadata.json beside dist/, written by astro build with the Wix hosting adapter | Released as a server-rendered site, by UploadHeadlessWebsiteFiles only: release a Wix Astro build. Never dropped. |
A build's dist/client/ dropped as static pages loses its server pages, and a
drop or patch on a site whose live release is a build replaces the build with
static files; nothing on the server stops that. Change it later
tells the two apart before any edit.
The JavaScript examples below run in ExecuteWixAPI, not directly in a shell
or browser. Without MCP, use the curl examples and edit downloaded files locally;
wix.request, wix.multipart and wix.headless.files are sandbox utilities,
not public SDK methods.
For external file inputs on either MCP tool, use only parameters in its visible schema.
Prefer resolved attachments: they transport files without retyping them.
If an attachment arrives unresolved as a local path, retrying that path cannot
work; use supported text input or another image route.
files text input uses this unescaped format:
Each path is relative to the site root. This format cannot carry a file
containing an identical === FILE: … === delimiter line. Binary images are
not text input.
Replace example IDs with response values: metaSiteId is the site ID, not
appId or uploadId. $AGENT / AGENT_SLUG is your short lowercase
attribution slug, or unknown-agent. Raw uploads use
?campaign=mcp&agent=$AGENT. Never leave a $NAME placeholder inside a
JavaScript URL.
Read linked skills from disk when installed; otherwise use ReadFullDocsArticle
when available for Wix skill/docs URLs, with web fetch as fallback.
Create only for a new site. Use the page title, at most 40 characters. Keep
origin: 'agent' and both synchronous steps:
The shell equivalent:
If UploadHeadlessWebsiteFiles is available, pass the existing
metaSiteId as siteId and the complete files. It creates no site,
replaces all files, adds attribution, and returns the live URL and dashboard.
Prefer a ZIP attachment containing the whole site, including images; a single
wrapping folder is stripped. Its limit is 10 MB per call. Otherwise use the
files text format above.
Without that tool, use ExecuteWixAPI. File inputs belong in the
external files or attachments parameter, not inside code; the script
receives them as the files global. For individual files:
For a single ZIP attachment, use
wix.multipart(await wix.unzip(files[0].content)) instead. Its entries must
already be site-root-relative; this local unzip does not strip a wrapper.
Do not hand-build multipart while wix.multipart exists.
An older execution schema without file inputs can pass inline entries to
wix.multipart([{ path, content }]). In JavaScript template literals, escape
backslashes first, then backticks and ${; otherwise text is reinterpreted.
Only if typeof wix.multipart !== 'function', construct multipart yourself:
parts named files, site-relative path as filename, CRLF line endings,
closing --<boundary>--, and the same boundary in the Content-Type header.
One request uploads and releases. Each part is named files; its
filename is the site-root-relative path, preserving subdirectories:
A single ZIP part also works: it is unpacked server-side with one wrapping
folder stripped. siteUrl is the final address.
This recipe releases a build you have. Making one is the Wix Headless kit's run
(https://www.wix.com/skills/headless-cold-start/headless-kit.md), which states
its own requirements: Node, a reachable npm registry, the project's install. A
sandbox with no network cannot make one.
Who releases from here: the build has to reach UploadHeadlessWebsiteFiles,
by one of two carriers. Check before starting, measured rather than assumed:
| Carrier | Check |
|---|---|
| Attachments | Your client passes attachments as files (a local path swapped for a download URL when the call is made). The tool's attachments description names the clients that do; a client that is not named, or that shows no way to pass a file, has no attachment carrier, whatever its shell can do. |
| A shell that reaches the internet | curl -sI -m 25 https://s3.amazonaws.com answers with S3's own headers: x-amz-request-id, and Server: AmazonS3 unless a proxy in front rewrote the Server header to its own name. The upload URLs are on Amazon S3; a sandbox limited to a few hosts gets a proxy's refusal instead, an HTTP answer with neither header. A sandbox proxy can take ten seconds to answer. |
With neither, no build is released from here. For a new site, publish static pages instead. For a site already released as a build, report that it cannot be changed from this environment, and change nothing.
A build goes through UploadHeadlessWebsiteFiles and nothing else: no drop
route takes it. The site stays the same, at the same URL, now server-rendered.
astro build, leaving out
node_modules, .env* and caches, and pass the ZIP as attachments. The
tool releases the build and stores the rest as the project's source in the
site's Code Store repository, where a later session continues from. A ZIP of
only .wix/ and dist/ releases too, storing no source.{ siteId, prepare: true, size }
with the ZIP's size (wc -c < build.zip) answers with curl commands for
the build ZIP and, when the site's repository takes it, a source ZIP; run them
as given, then make the second call it names, with uploadKey. A 404 on the
second call means the upload URL expired: prepare again. A prepared upload is
finished by that second call and by nothing else: a release made another way
(the ZIP attached, say) leaves the prepared upload behind and releases without
its source, so a prepare that failed on the way is prepared again, not replaced
by another carrier.expectedHeadRevisionId is the repository revision the project started from:
the headRevisionId the prepare returned for a project made in this run, or
the revision.id it was downloaded at.
EXPECTED_HEAD_REQUIRED or a 409 means the repository holds newer work:
download it, carry the change over, and release again.
Reuse the ID from the earlier upload. If only a name or URL is known, query the
user's sites and match it; ask if ambiguous. The default namespace omits
headless sites, so explicitly filter HEADLESS:
Omit the id filter when looking up by name/URL. While data.metadata.hasNext,
follow data.metadata.cursors.next with { query: { cursorPaging: { limit: 100, cursor } } }. See Query Sites for name search on large accounts.
With a known ID, use $in, not a scalar ID or $eq (rejected).
Unclaimed anonymous sites instead use their saved ID and
anonymous upload/release.
Use the same site ID and URL. The file helpers and the ZIP fallback below edit static pages: they read the site's last drop and drop the result. A site whose live release is a Wix Astro build is changed in its project instead; a drop or patch there replaces the build with static files, and the server does not stop it. So first tell what is live.
When this conversation published the site, or the user says how it was made,
you know. Otherwise one read-only call tells, in ExecuteWixAPI:
| Answer | What is live | Do |
|---|---|---|
200 | Its last static drop | Edit the static pages. |
409 LIVE_SITE_CHANGED | A release replaced the last drop: a Wix Astro build | Change the project. No drop, no patch. |
404 SITE_CODE_NOT_FOUND | Nothing was ever dropped | Open the live URL. Pages there mean a build released without a drop: change the project. Nothing there means an empty site: drop as for a new one. |
The project lives in the site's Code Store repository, which is the managed
project whose id is the site's app id: appId in wix.config.json, or the
companion app. In ExecuteWixAPI, in site scope:
curl -o project.zip "<downloadUrl>" (the
URL is signed and lasts 5 minutes; ExecuteWixAPI cannot fetch it), unpack,
npm ci, make the change, npx astro build, and
release the build with revision.id as
expectedHeadRevisionId. The download holds the project without
node_modules or .env* files. Without a shell that can build, report that
the site is a server-rendered project and change nothing.NOT_FOUND, GIT_BRANCH_NOT_FOUND), or a repository whose root
holds index.html rather than a project: the project's source was not
stored with the release. The site can be changed only from the project where
it was built; report that, and change nothing here.With ExecuteWixAPI, prefer its file helpers
for selective edits, not UploadHeadlessWebsiteFiles: sending only changed
files to that upload tool would remove omitted files. Without MCP, go directly
to the ZIP fallback.
The server may have helpers newer than the client's visible description.
Check once in ExecuteWixAPI without changing anything:
If false, use the ZIP fallback. Do not retry a
missing helper or invent UploadHeadlessWebsiteFiles parameters such as mode: 'merge'.
These execution helpers do not change the full-replacement upload contract.
List/read/inline edits work without external file inputs.
Omit prefix to list all paths. Default limit is 100, maximum 200; advance
offset by the limit while hasMore. The size field is bytes, not size.
text is a string, not a method. Read supports UTF-8 text up to 256,000
bytes; use the ZIP fallback for larger/binary files. Helpers exclude the
generated wix.config.json. Each list/read fetches a fresh full snapshot, so
request only needed paths/pages; for bulk inspection download once instead.
For a small edit, keep the original in the execution sandbox and guard the replacement. Do not return/retype the whole file just to change one string:
For authored additions/replacements, pass only changed files through supported external file inputs, then:
If the visible ExecuteWixAPI schema has neither files nor attachments,
do not reference the files global. Supply changed text files inline as
files: [{ path: 'hours.txt', content: 'Mon–Fri 8–5' }] instead. Helper
availability does not imply file-input support; use the ZIP/shell fallback
for binary additions that cannot be supplied.
deletePaths; omit it
when nothing should be removed. Delete-only: files: [] plus exact paths.wix.config.json
are rejected. Keep a root index.html or index.htm in the final site.Inspect summary.added/replaced/deleted/unchanged/missingDeletes.
unchanged means supplied files already matched byte-for-byte; omitted files
are preserved too. published: false means no byte change, not failure.
A publish returns siteUrl, uploadId, fileCount. Then
verify.
For older servers, bulk/binary reads, or a shell workflow, obtain the current ZIP rather than reconstructing the site from memory:
In an execution script, use responseType: 'base64' and feed data directly
to wix.unzip. Entries contain { path, bytes, text() }: unlike helper
reads, text() is a method. Call it only on known text files. Here is a
complete guarded edit; untouched assets remain bytes:
Reuse the download/unzip portion for these operations:
| Operation | Script after obtaining entries / byPath |
|---|---|
| List only | return entries.map(e => ({ path: e.path, bytes: e.bytes.length })); No drop. |
| Read only | Find the exact entry, fail if missing, then return entry.text(); for a known text file. No drop. |
| Add/replace | byPath.set('hours.txt', { path: 'hours.txt', content: 'Mon–Fri 8–5' }); |
| Delete | byPath.delete('old-offer.html'); |
| Merge supplied individual files | for (const e of files) byPath.set(e.path, e); |
For a single supplied ZIP, use await wix.unzip(files[0].content) instead of
files in that merge loop; its entries must be site-root-relative.
For mutations, validate paths and the final homepage, then drop all map
values. Leave wix.config.json out; preserve other unchanged entries as bytes.
Never pass only a delta to a raw drop. For a shell edit, likewise preserve the
downloaded files and upload the complete result.
If snapshot access is blocked by host policy, use authenticated curl or request the complete current ZIP. Do not keep retrying, bypass the policy, or claim to preserve files you could not retrieve. A failed file lookup is not evidence that the site is empty.
If a call fails after publishing, inspect the current snapshot before retrying: the write may have succeeded and only its subsequent verification failed. Avoid repeating a mutation merely because the last call returned an error.
Give the live URL, what changed, what was verified, and any missing assets.
On first authenticated publish (or after claim), also give
https://manage.wix.com/dashboard/{metaSiteId} and
domain connection options
for a Wix-purchased or existing domain. For an unclaimed anonymous site, give
the live URL and private save link instead, not a dashboard link.
A static page cannot itself provide stores, payments, bookings, CMS, members or forms. For an uploaded mock, publish it, then name the nonfunctional buy/book/order/join/submit/pay controls, what Wix solution and content would be created, and ask before connecting. A request for a working shop or real bookings already supplies that consent; do not stop at a static mock.
Use the headless skill on the same site, appId and URL, not another site. Claim an anonymous site first.
https://www.wix.com/skills/headless-cold-start/headless-kit.md. It installs
the skills, checks CLI login, then hands off to the kit for deployment,
seeding, build and release. Start from the site's downloaded ZIP, unpacked
into a project folder.https://www.wix.com/skills/wix-headless-kit/guides/api-run.md for the API
route, then upload the frontend as above.SVG is text; other images are bytes. Prefer, in order:
UploadImageToWixSite when available using imageUrls; use its returned
static.wixstatic.com URL. A sharing page is not a direct file URL.imageBase64 with mimeType, one image per call, if the
tool exposes those fields. Never synthesize base64 in
model output: it causes decoding errors or corrupt images.https://manage.wix.com/dashboard/{metaSiteId}/media-manager.
Then list media, match displayName,
update references to returned public URLs and republish.Never substitute drawings/placeholders for supplied images, shrink/re-encode them, or decode unchanged binary files as text during edits.
With shell access to Wix but no login, generate one UUID as anonymousId
and reuse it with the returned metaSiteId. The temporary site expires after
one hour unless kept; expired calls, including claim, return 404.
For edits, repeat upload and release with the full set. When finished, claim with the user's identity or give them the live URL and this save link:
The link signs them in to keep the site. Whoever opens it while signed in gets the site; share it only with the user.
Claim last: it ends the anonymous record. Later edits use authenticated drops
or patches. The URL changes on claim; obtain the new sites[0].viewUrl
with the HEADLESS site query.
https://www.wix.com/headless/drop?utm_campaign=mcp&agent=<your-agent-id>
When no upload route works, the user can drag in files without login; the site goes live and a banner offers to keep it. Explain the limits below and what would enable you to publish directly: a network-enabled shell or connected Wix MCP.
index.html. The patch helper requires a root
index.html or index.htm.UploadHeadlessWebsiteFiles limits each call
to 10 MB. Patch also limits the final snapshot to 2,000 files.| Failure | Action |
|---|---|
400 with MISSING_INDEX_HTML, FILE_TOO_LARGE, TOTAL_TOO_LARGE | Read details.applicationError.code and fix the files. |
Drop 403 PERMISSION_DENIED | Check token freshness and access to this site. An unclaimed anonymous site requires upload/release, not drop. |
409 LIVE_SITE_CHANGED from the files endpoint or a patch | The live site is a Wix Astro build, not its last drop: change the project. Do not drop. |
Release 400 EXPECTED_HEAD_REQUIRED / 409 | The repository holds newer work than the project started from: download it, carry the change over, release again. |
Drop/upload 500 | Inspect current state before retrying once; retain the request ID if it fails again and offer the drop page. Hand-built malformed multipart can also cause this: use wix.multipart. |
Anonymous 404 | The hour expired or the site was claimed; do not create a replacement without explaining. |
Offer feedback to Wix when a route fails, retries or workarounds are needed, images must wait for the user, or guidance is missing.
Last updated: 25 October 2026