
Description
Build on Wix from the Base44 builder sandbox — this file is the complete guide (site context, doc discovery, API contracts, who calls Wix from where), and scripts/utils.js is its helper module, loaded from disk per exec. Triggers: build a Base44 app on a connected Wix site, look up a Wix API from the sandbox, decide frontend vs backend for a Wix call.
SKILL.md
Building on Wix from Base44
The Wix connector is connected; this is how the app gets built on it — gather the site's context, find the APIs and learn their contracts from the docs, write the code. Discover everything: endpoints, paths, doc URLs, request and response fields all come from the calls below, never from memory or pattern. 404 or empty ⇒ discover, not permute. Examples teach mechanics and go stale — verify before relying.
What are you building?
The app's audience picks the token, and the token picks the architecture: the visitor token is
public — anyone can mint it from the site's clientId — and the admin token is a secret,
the connector's, server-side only.
browser ──(visitor token)─► wixapis.com the visitor's own reads & actions
base44/functions/* ──(admin token)───► wixapis.com work that needs the owner's identity
exec_tool ──(admin token)───► wixapis.com you: ad hoc probing/managing while building
A site for visitors — storefront, blog, booking. Headless means the Wix site has no pages of
its own: your app IS its frontend. The complete visitor experience —
every page, every read, every action a visitor takes — is browser calls on the visitor token;
none of it needs a backend function. Public reads
included (the visitor token queries the catalog directly), and carts/current/* + checkout act
on the CALLER's cart, so only the visitor token reaches the visitor's cart. One file carries it
all: src/lib/wixClient.js (Write the code, below). base44/functions/* appear only where work
needs the owner's identity — elevated-permission ops a visitor triggers, webhooks, scheduled
jobs — and for the app's non-Wix backend.
An admin tool for the owner — dashboard, back office. The pages act as the owner, whose token
is a secret: pages → base44/functions/* ──(admin token)──► wixapis.com.
The helpers
Research and probing run in exec_tool, and one loader opens every exec (execs share no state — reload each round; the module lives on disk next to this file, network only as first-touch fallback):
const fs = require("fs"), P = ".agents/skills/wix-base44-connector/utils.js";
if (!fs.existsSync(P)) { fs.mkdirSync(".agents/skills/wix-base44-connector", { recursive: true });
fs.writeFileSync(P, await (await fetch("https://www.wix.com/skills/wix-base44-connector/scripts/utils.js")).text()); }
const wx = (() => { const m = { exports: {} };
new Function("module", "exports", "require", fs.readFileSync(P, "utf8"))(m, m.exports, require); return m.exports; })();
Results ≤ 4,000 chars come back inline (exec results clip at ~5,000); anything bigger is saved
under .agents/skills/wix-base44-connector/scratch/ and returns { path, bytes, lines, outline } —
the outline is the map. Read a saved file the way you already know how:
wx.bash("grep -n 'term' <path> | head -40") to find (GNU grep/sed, awk is mawk, no rg;
across everything saved: grep -rn 'term' .agents/skills/wix-base44-connector/scratch/), and
read_file to quote — a window via offset/limit at the lines grep named, or the whole
file when it fits read_file's 45K cap. API responses are site data and never land in
scratch — project them to facts. Fetch every URL inside exec with fetch() — website/browser
tools clip at 10,000 chars silently. One exec per round; timeout 10s, up to 120 via {timeout}.
Gather context — the dynamic context report
const { accessToken } = await base44.asServiceRole.connectors.getConnection("wix");
return await wx.context(accessToken, "Apps"); // no section arg → the report's outline
One report: installed apps with ids (incl. Stores' catalog version — V1 vs V3 decides its
endpoints), the OAuth app id (also the visitor clientId), locale, currency, CMS collections.
An empty report = bad token, never an empty site.
Learn Wix — find the APIs, learn their contracts
// know the product? browse is deterministic — menuUrl alone orients (children + counts);
// filter before listing methods, unfiltered listings clip
await wx.browse("https://dev.wix.com/docs/api-reference/business-solutions/bookings/bookings",
{ include: ["METHOD"], filter: "resched", depth: 4 });
// don't know where it lives? search ranks, never says "no match" — drop wrong-product hits
await wx.search("pause a pricing plan subscription and resume it");
// → { hits: [{ method, endpoint /* callable */, docsUrl, gist }] } — hits often ARE the answer
Go deeper for fields, enums, or absence — only the spec index proves absence.
Read a doc page
Method pages are 100 KB+, twin REST and SDK halves repeating field names at different types — map and window in the SAME exec; coordinates are for your code, not for a second round:
const pg = await wx.page(docsUrl); // whole text when small; else { path, bytes, lines, outline }
// your half, windowed to your term and its Examples (a complete working request — URL, headers,
// body — usually all you need), one round:
return wx.bash(`sed -n '/^## REST API/,/^## JavaScript SDK/p' ${pg.path} | grep -B5 -A40 -i 'examples\\|<term>' | head -c 3800`);
// a giant fenced example → its field vocabulary instead of paging it:
// wx.bash(`sed -n '<a>,<b>p' ${pg.path} | grep -oE '"[a-zA-Z]+":' | sort -u | head -60`)
search also saves its raw content beside the inline hits — grep its path when a hit's six
lines weren't enough.
The spec index
Answers questions about pages you already found — arrive with a docsUrl, match by it. Also the
only proof an API does NOT exist:
await wx.spec(`
const r = lightIndex.find(x => x.docsUrl === "<docsUrl from browse/search>");
return r.methods.map(m => ({ op: m.operationId.split(".").pop(), verb: m.httpMethod,
call: m.publicUrl })); // publicUrl is callable — path is PARTIAL
`);
// request fields next round: getResourceSchemaByUrl(docsUrl) →
// m.requestBody.content["application/json"].schema.properties — names and types, drill deeper
// per round; $circular stubs resolve via s.components.schemas["<name>"]
Management recipes — check before composing admin flows
~100 curated multi-step recipes (install apps, seed catalogs, set up whole verticals):
await wx.recipes(); // categories with counts
await wx.recipes("stores"); // a category's list — or any task word: wx.recipes("coupon")
await wx.page(url); // read the chosen recipe — whole when small, saved + outline when
// big; then grep / read_file windows by the outline's line numbers
A matching recipe beats composing the flow from single endpoints: it carries ordering and cross-step gotchas no method page mentions.
Write the code
Response shapes obey the discover rule in every lane: code against fields you saw in a live response or the schema — remembered names are often from older versions. Probe one real row first.
Admin calls — exec ad hoc, backend functions deployed
const { accessToken } = await base44.asServiceRole.connectors.getConnection("wix");
const data = await wx.post("https://www.wixapis.com/contacts/v5/contacts/query", // spec-index publicUrl
{ query: { cursorPaging: { limit: 10 } } }, accessToken);
// let it throw — the thrown message is your result; .catch hides the answer
return wx.clip({ error: null, count: data.contacts?.length, first: data.contacts?.[0] });
// ↑ one real row, whole — the shapes you code against live here, not in key names
The same call deploys as base44/functions/* (work the app does as the owner) — shipped code
carries its own four-line fetch; the helpers are a build tool, not a runtime dependency.
A visitor client — src/lib/wixClient.js
The "site for visitors" shape (What are you building?), in code — one file pages import. Neither
clientId (from the context report) nor the minted token is a secret; together they are "an
anonymous visitor", safe in shipped code:
// src/lib/wixClient.js
let token;
const mint = async (body) => {
const r = await (await fetch("https://www.wixapis.com/oauth2/token", { method: "POST",
headers: { "Content-Type": "application/json" }, body: JSON.stringify(body) })).json();
token = r.access_token; sessionStorage.setItem("wixRefresh", r.refresh_token); // expires_in: 14400s = 4h
};
// first visit: mint({ clientId: WIX_CLIENT_ID, grantType: "anonymous" });
// on expiry: mint({ refreshToken: sessionStorage.getItem("wixRefresh"), grantType: "refresh_token" });
// a fresh anonymous mint is a NEW visitor — the old one's cart goes with it
export const wix = (path, opts = {}) => fetch("https://www.wixapis.com" + path, { ...opts,
headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json" } });
Token contract: …/headless/authentication/retrieve-tokens. Prove the lane in one exec before
writing pages — mint a visitor, make one public read with it:
const { access_token } = await wx.post("https://www.wixapis.com/oauth2/token",
{ clientId: WIX_CLIENT_ID, grantType: "anonymous" }); // clientId: from the context report
return await wx.post("<a public read from Learn Wix>", { query: {} }, access_token);
// 200 ⇒ every visitor-facing page in the app is this same call, no server between
// a lean default response isn't the whole shape — contracts often define a fields param
// that opts INTO heavier parts (formatted prices, media); read the contract for it
More skills from the skills repository
View all 9 skillsreplatform
route RePlatform source-to-Wix migrations
Aug 9AutomationMigrationWixwix-app
build Wix CLI app extensions
Aug 22BackendCLIFrontendWixwix-auth
authenticate with Wix APIs
Jul 7API DevelopmentAuthWixwix-design-system
build UIs with Wix Design System
Aug 19DesignDesign SystemFrontendUI Components +1wix-docs
lookup Wix API documentation
Aug 19API DevelopmentDocumentationWixwix-headless
build Wix Managed Headless sites
Aug 13DesignFrontendWeb DevelopmentWix