Skill 16 · Wix Headless Templates
Subchapter 16.785
storefront/INSTRUCTIONS.mdMarkdown48 KBView on GitHub
The commerce machinery ships as files — data layer, hooks, cart, checkout, SEO plumbing, typed end-to-end. The presentation doesn’t ship — you build it on the shipped hooks/DTOs: the shop, category, and product (PDP) pages with their islands, the home page, and the brand. The skeletons below carry each page’s contract — including the SSR and SEO machinery, which must be exact. You never write commerce code; you never skip designing the store.
On Astro and React the shipped files are tested and work as they are — this table and the
contracts below are everything you need to use them, so don’t spend the run reading their source;
wire them and build your surfaces. Reading them is the right move when something is off (a runtime
error, a field this playbook doesn’t cover) or when the brief wants a behaviour they don’t offer —
then read the file that owns it and change or extend it. On lib, static, and a port the
components don’t deploy at all, and each wiring section below opens with the files to read before
writing their equivalents. Files you edit: SiteLayout.astro and styles/global.css.
Files you create (skeletons below): the shop, category, and PDP pages with their island
components, plus your home page.
| file | what it is |
|---|---|
wix/config.ts · wix/sdk.ts | shared auth seam (deploy configures it — nothing to set by hand) |
wix/media.ts · wix/money.ts | imgAttrs(url, sizes) — every <img> attribute for a DTO image (src, srcSet, sizes, lazy): <img {...imgAttrs(p.imageUrl, "25vw")} alt={p.name} />; imgSrc() / imgSrcSet() / formatMoney() underneath, already used by everything shipped |
wix/storefront/types.ts | the DTOs (ProductSummary, ProductDetail, Cart, Category, Facet, SubscriptionPlan) — contracts inlined below |
wix/storefront/catalog.ts | searchCatalog (sort/filter/facets/search + cursor paging + result count, all server-side), fetchFacets / fetchFacetData (from search aggregations — the whole catalog, not a sample), fetchProducts, fetchProductsByCategory, fetchProductBySlug, fetchCategories, fetchCategoryBySlug, fetchAllProductsCategoryId, fetchInventory (stock left / pre-order per variant), fetchBackInStockEnabled + requestBackInStock (notify me), resolveVariant, choiceAvailability — the transport; the rules and DTO mappers are in catalog-core.ts / cart-core.ts beside it (shared with the REST layer) |
wix/storefront/cart.ts · cart-store.ts | Cart V2 (add / quantity / remove / coupon / note / checkout; totals from the cart estimate) + shared cart state (module store — spans Astro islands) |
wix/storefront/shop-store.ts · product-detail-store.ts | the listing and product-detail state machines, framework-free (createShopStore(), createProductDetailStore() — getState/subscribe + actions, one instance per surface); the hooks below bind them to React, every other stack uses them directly |
hooks/storefront/useCart.ts | cart state + actions — contract below |
hooks/storefront/useShop.ts | React binding of shop-store.ts: category scope, sort, filters, option facets, result count, paging — contract below |
hooks/storefront/useProductDetail.ts | React binding of product-detail-store.ts: option selection → variant resolution → add-to-cart — contract below |
components/storefront/CartButton.tsx · CartDrawer.tsx | header badge + slide-over cart (lines with struck discounted prices and stock caps, promo code, note to seller, the totals breakdown, checkout) — wire as-is (drawer once per page) |
components/storefront/FilterPanel.tsx | the gallery’s filter LAYOUT — toolbar (result count, sort), active chips, then a 16rem sidebar of collapsible groups (price as a two-handle slider bounded by the catalog’s real prices, availability, one group per facet — an option or a choice modifier — with swatches/pills) beside YOUR results; a bottom sheet under md — wire as-is in your ShopView, your grid as its children: <FilterPanel shop={shop}>…grid…</FilterPanel> |
components/storefront/QuickAdd.tsx | the tile’s purchase control — one click for a product with no options, a picker anchored to the tile (bottom sheet on small screens) for one with options, the product page for free-text customization or a subscription plan — wire as-is as the last row of every tile’s text block (<QuickAdd product={p} />) |
components/storefront/OptionPicker.tsx | the purchase controls for one product — option groups (swatches/pills, choices unavailable with the current picks disabled), choice and text modifiers (with the merchant’s character limit), the plan picker of a subscription product, an optional quantity stepper capped at the stock left (“Only N left”), the buy button gated by useProductDetail with its plain reason, “Pre-order” with the merchant’s note when pre-orderable, “notify me” when sold out and the merchant collects requests — wire as-is in your PDP (<OptionPicker detail={d} showQuantity />); QuickAdd’s picker is this same component |
components/storefront/ShopView.tsx · ProductDetailView.tsx | don’t ship — YOU create them (skeletons below): the client islands your shop, category, and PDP pages mount |
styles/global.css | the design system: Tailwind v4 + the @theme token block (colors, radii, fonts — same token family as the official Wix templates). Everything, shipped and yours, styles from these tokens |
Astro stack additionally gets:
| file | what it is |
|---|---|
layouts/SiteLayout.astro | the site chrome — yours to brand: header, footer, nav. Keep the <slot name="seo-tags" />, the global.css import, and the CartButton/CartDrawer mounts |
pages/shop.astro · pages/category/[slug].astro · pages/products/[slug].astro | don’t ship — YOU create them (skeletons below): thin SSR pages that fetch server-side and mount your islands; the category and PDP pages carry the owner-editable SEO machinery, exactly as their skeletons show |
You implement three surfaces yourself — each an SSR page plus its island, per the
skeletons below — styled with Tailwind utilities on the @theme tokens, designed to fit the
brief (the business, the tone, the audience — a toy brand and a jewelry house should not get
the same store):
ShopView (skeletons below) — category links, the shipped
FilterPanel, and your grid of your tiles: image treatment, badges, price/sale
presentation, hover behavior, grid rhythm (columns, density, maybe an editorial featured
tile), each tile carrying the shipped QuickAdd. products === null → skeleton tiles,
[] → an honest empty state. Decompose into ProductCard/ProductGrid files if you like —
your call, nothing prescribes it. The category page (/category/[slug]) mounts the same
ShopView scoped to one category — one island, two pages.ProductDetailView (skeletons below) — gallery, price/sale,
description, your option-selection UI (color options = real swatches), quantity,
add-to-cart — on useProductDetail, which owns ALL selection/variant logic; you own how it
looks. This is the surface that most often looks generic — make the layout the brand’s: an
editorial split, a sticky buy column, a full-bleed gallery.Plus the theme (edit the @theme block in styles/global.css — one edit; a dark brand is
flipped token values; add brand fonts as extra tokens) and the chrome (header/footer in
SiteLayout.astro, one edit pass; mount the shipped CartButton in your header).
These are the recommended defaults for a store whose brief says nothing about them. They are not requirements: when the user’s prompt asks for something different — a cart page instead of a drawer, no shop page, a particular layout or look — the prompt wins, and the item here that conflicts with it is dropped without discussion. Look at the catalog before designing (categories, assortment size, media, options, sales, ribbons) and design for this store, not for a stereotype of its category. Then, by default:
/category/<slug>) —
never to an anchor on the shop page that every tile shares — and its image is a product from
THAT category (fetched with the category filter) or the category’s own image, never a
positional guess into the all-products list.FilterPanel around the grid (toolbar, then a filter sidebar beside the results on desktop and
a sheet on phones) — a store with any filterable catalog ships it, not “when it fits”; the
shipped QuickAdd as the last row of every tile; each
category reachable by a real link (/category/<slug>) — from the nav, the home page, or the
shop’s category row; loading, empty, no-results, and error states that look different.blockedReason
in the first screen at 390px wide too — on a phone the image is a bounded band
(max-h-[45vh]), not a full-screen hero that pushes the price below the fold; every ribbon;
every image reachable in the gallery; the main image follows the selection (imageUrl from the
store when set, else the gallery’s first); breadcrumbs (Home › the category path › the product)
when the product has a main category.backdrop-blur header gets clipped), lock background scroll, close on Escape,
return focus on close — as the shipped CartDrawer does. An anchored picker is positioned
inside its tile, not fixed with computed offsets. If you add dismiss-on-outside-click, decide
inside/outside before anything re-renders (a capture-phase listener), or a click on a swatch
that re-renders the panel reads as “outside” and closes it.Pre-order (isPreorder, the merchant’s note), subscription plans (the picker), stock left, and
notify-me ship in the components and show only when the catalog has them. Product groups and
promotions are built only when the catalog has them — never fabricated; wix-docs has their
contracts.
// ProductSummary (grid tiles) — all display-ready: prices formatted, images https URLs:
// { id, slug, name, price, maxPrice, fromPrice: boolean, compareAtPrice|null,
// discountNames: string[] /* automatic discount rule names — under the price */,
// pricePerUnit: string|null /* "€2.50 / 100 g" as Wix formats it */,
// ribbon|null, ribbons: string[], minPriceVariantId|null,
// availability: "IN_STOCK"|"OUT_OF_STOCK"|"PARTIALLY_OUT_OF_STOCK", preorder: boolean,
// hasSubscriptions: boolean, imageUrl, hoverImageUrl, optionsSummary /* "2 colors · 3 sizes" */,
// swatches: string[] /* hex colors of a color option's choices — dots on the tile, not a picker */,
// quickAddable: boolean }
// fromPrice → render "From {price}" (the cheapest variant is discounted and variants differ in
// price: the discounted top is only known on the PDP; maxPrice equals price then). Else
// price !== maxPrice → the product is a RANGE: render "price – maxPrice" (compareAtPrice is null
// then — never a struck price beside a range). Otherwise price is what the buyer pays (a discount
// already applied) and compareAtPrice, when present, is the labelled "was".
// ribbons = EVERY merchant ribbon, primary first — render all, one shared style; a ribbon is a
// label, never proof of a discount. The tile's buy control is the shipped <QuickAdd product={p} />:
// it reads quickAddable / minPriceVariantId / preorder / hasSubscriptions itself and routes to a
The class names in these skeletons are the Astro/React spelling of layout rules that hold on
every stack, and each rule is also named in words beside its classes. On a stack where the shipped
components don’t deploy (lib, static, a port), keep the rule and write it in your own CSS.
Nothing renders until you write these — the store IS your work. Each page is a thin SSR shell
(fetch → DTO props → island); each island is a thin view over a hook. The pages’ frontmatter
is machinery, not design — reproduce it as the skeletons show, exactly. Hooks first,
branches after (an early return above a hook changes hook order between renders and React
throws). The islands render on the server too (client:load SSRs), and by then the 200 and
headers are already sent — a render throw truncates the body mid-stream and surfaces to the
visitor as ERR_HTTP2_PROTOCOL_ERROR, not an error page. Render every state totally; nothing
in a render path may throw.
---
// src/pages/shop.astro — YOU create it. Products and categories are fetched SERVER-SIDE
// (SEO: view-source shows product names) and handed to the island as serialized DTO props;
// category switches then filter live on the client.
import SiteLayout from "../layouts/SiteLayout.astro";
import ShopView from "../components/storefront/ShopView";
import { fetchProducts, fetchCategories } from "../wix/storefront/catalog";
import type { Category, ProductSummary } from "../wix/storefront/types";
let products: ProductSummary[] = [];
let categories: Category[] = [];
try {
[products, categories] = await Promise.all([fetchProducts({ limit: 24 }), fetchCategories()]);
} catch {
// Guarded: an unhandled SSR throw truncates the response mid-stream; the island
// renders your empty state instead.
}
---
<SiteLayout title="Shop">
<!-- your page heading / intro, then: -->
<ShopView client:load initialProducts={products} initialCategories={categories} />
</SiteLayout>---
// src/pages/category/[slug].astro — YOU create it. A category is a Wix ITEM PAGE like a product:
// its own URL, its SEO owner-editable in the dashboard, registered in the sitemap. Same SEO
// machinery as the PDP with the CATEGORY identifiers. It mounts the SAME ShopView, scoped.
import SiteLayout from "../../layouts/SiteLayout.astro";
import ShopView from "../../components/storefront/ShopView";
import { fetchCategoryBySlug, fetchProductsByCategory, fetchCategories } from "../../wix/storefront/catalog";
import type { Category, ProductSummary } from "../../wix/storefront/types";
import { WIX_APPS } from "@wix/essentials";
import { SEO } from "@wix/seo/components";
import { loadSEOTagsServiceConfig } from "@wix/seo/services";
import { seoTags } from "@wix/seo";
export const wixMetadata = {
appDefId: WIX_APPS.checkoutAndOrders.id,
pageIdentifier: WIX_APPS.checkoutAndOrders.categoryPageMetadata.pageIdentifier,
identifiers: { slug: WIX_APPS.checkoutAndOrders.categoryPageMetadata.identifiers.collection },
};
const slug = Astro.params.slug!;
const forwardedUrl = Astro.request.headers.get("x-wix-forwarded-url");
const pageUrl =
forwardedUrl && URL.canParse(forwardedUrl) && /^https?:$/.test(new URL(forwardedUrl).protocol)
? forwardedUrl
: Astro.url.href;
let category: Category | null = null;
let products: ProductSummary[] = [];
let categories: Category[] = [];
let seoTagsServiceConfig = null;
try {
category = await fetchCategoryBySlug(slug);
if (category) {
// A sub-category is its own SEO item type (the owner's settings differ per type).
const itemType = category.parentId ? seoTags.ItemType.STORES_SUB_CATEGORY : seoTags.ItemType.STORES_CATEGORY;
[products, categories, seoTagsServiceConfig] = await Promise.all([
fetchProductsByCategory(category.id, { limit: 24 }),
fetchCategories(),
loadSEOTagsServiceConfig({ pageUrl, itemType, itemData: { slug } }),
]);
}
} catch {
// Guarded: an unhandled SSR throw truncates the response mid-stream.
}
// A missing or hidden category is a real 404 — never a fallback to all products.
if (!category) {
return new Response(null, { status: 404 });
}
---
<SiteLayout title={category.name}>
<SEO.Tags seoTagsServiceConfig={seoTagsServiceConfig} slot="seo-tags" />
<!-- your heading: category.breadcrumbs as links (Home › each ancestor → `/category/${b.slug}`) when
it has any, category.name, category.description when present, category.imageUrl as a banner
when it fits the design, then: -->
<ShopView client:load initialProducts={products} initialCategories={categories} initialCategoryId={category.id} />
</SiteLayout>---
// src/pages/products/[slug].astro — YOU create it. This is a Wix ITEM PAGE: the wixMetadata
// export + <SEO.Tags> are what let the site owner edit this page's title/description/OG in
// the dashboard and register the route in the sitemap. All three SEO pieces are REQUIRED,
// exactly as here.
import SiteLayout from "../../layouts/SiteLayout.astro";
import ProductDetailView from "../../components/storefront/ProductDetailView";
import { fetchProductBySlug } from "../../wix/storefront/catalog";
import { WIX_APPS } from "@wix/essentials";
import { SEO } from "@wix/seo/components";
import { loadSEOTagsServiceConfig } from "@wix/seo/services";
import { seoTags } from "@wix/seo";
export const wixMetadata = {
appDefId: WIX_APPS.checkoutAndOrders.id,
pageIdentifier: WIX_APPS.checkoutAndOrders.productPageMetadata.pageIdentifier,
identifiers: { slug: WIX_APPS.checkoutAndOrders.productPageMetadata.identifiers.handle },
};
const slug = Astro.params.slug!;
// Behind Wix's proxy the request URL is the internal one — the real public page URL arrives
// on x-wix-forwarded-url, and the SEO service needs the public one.
const forwardedUrl = Astro.request.headers.get("x-wix-forwarded-url");
const pageUrl =
forwardedUrl && URL.canParse(forwardedUrl) && /^https?:$/.test(new URL(forwardedUrl).protocol)
? forwardedUrl
: Astro.url.href;
let product = null;
let seoTagsServiceConfig = null;
try {
[product, seoTagsServiceConfig] = await Promise.all([
fetchProductBySlug(slug),
loadSEOTagsServiceConfig({
pageUrl,
itemType: seoTags.ItemType.STORES_PRODUCT,
itemData: { slug },
}),
]);
} catch {
// Guarded: an unhandled SSR throw truncates the response mid-stream.
}
if (!product) {
return new Response(null, { status: 404 });
}
---
<SiteLayout title={product.name}>
<SEO.Tags seoTagsServiceConfig={seoTagsServiceConfig} slot="seo-tags" />
<ProductDetailView client:load initial={product} />
</SiteLayout>// src/components/storefront/ShopView.tsx — YOU build it; shop.astro AND category/[slug].astro mount it.
import { useShop } from "../../hooks/storefront/useShop";
import FilterPanel from "./FilterPanel";
import QuickAdd from "./QuickAdd";
import { imgAttrs } from "../../wix/media";
import type { Category, ProductSummary } from "../../wix/storefront/types";
export default function ShopView(props: {
initialProducts?:
// src/components/storefront/ProductDetailView.tsx — YOU build the whole PDP surface;
// your [slug].astro mounts it with the server-fetched product.
import { useProductDetail } from "../../hooks/storefront/useProductDetail";
import OptionPicker from "./OptionPicker";
import type { ProductDetail } from "../../wix/storefront/types";
export default function ProductDetailView(props: {
initial?: ProductDetail | null; // SSR (Astro); a SPA passes { slug } instead
slug?: string
On lib, static, and a port, nothing under components/ or hooks/ arrives (on static, read
static.md in this folder). The state
machines behind the hooks do arrive — wix/storefront/shop-store.ts, product-detail-store.ts,
cart-store.ts — so you never rewrite them: create a store per surface, subscribe, render from
getState(), call its actions. Their *State interfaces are the render contract; read those.
What you write is the rendering — grid, product page, picker, filter panel, drawer — and for that
read these first; they are tested code for exactly that behaviour, and rewriting them from the
prose above is where the bugs come from:
components/storefront/QuickAdd.tsx — the three purchase paths decided from the summary DTO;
the panel is positioned inside the tile (the tile is relative), a bottom sheet on small
screens, never fixed with computed offsets; it closes on Escape, the close button, a
successful add, or the scrim — there is NO outside-click handler (one that runs after a
re-render sees the clicked swatch detached and closes on every pick).components/storefront/OptionPicker.tsx — the purchase controls as working code: swatches vs
pills, choices unavailable with the current picks disabled, text modifiers with their limit,
the plan picker, quantity capped at the stock left, the gated button with its reason, the
“Pre-order” label and note, the notify-me form; the PDP and the tile picker share it.components/storefront/CartDrawer.tsx — the overlay contract as working code: root-level,
scrim, scroll lock, Escape, focus in and back — plus the cart’s contents: struck discounted
lines, per-status copy, the stock cap on “+”, promo code, note to seller (saved on blur), the
totals breakdown (subtotal, discounts, fees, taxes unless prices include them, total).components/storefront/FilterPanel.tsx — inline commits at once, the sheet stages until Apply;
the price pair commits only when valid.All under templates/storefront/app/.
@theme tokens (one edit); brand SiteLayout.astro (one pass).client:load with the
SSR props; browser-state widgets (cart) are client:only="react".pages/index.astro (home) on SiteLayout.Read the reference files listed above before writing any surface.
deploy.mjs storefront --stack lib put the data layer in src/wix/ and nothing else: sdk.ts
(the visitor client, configured with the public client id), media.ts, money.ts, and
wix/storefront/ — catalog.ts, cart.ts, types.ts, the *-core.ts rules, and the three
stores shop-store.ts, product-detail-store.ts, cart-store.ts. None of it is React. The
hooks and components don’t ship on this stack; the stores replace the hooks, and you write the
components in your framework to the contracts on this page:
shallowRef updated in
subscribe; Svelte: readable(store.getState(), (set) => store.subscribe(() => set(store.getState())));
Solid: a signal set in subscribe). createShopStore(options) per listing (start() when
mounted, stop() when unmounted), createProductDetailStore({ initial | slug }) per product
surface, the cart store as-is (module-level). State in, actions out — exactly the hooks’
contracts above;FilterPanel.tsx, QuickAdd.tsx, CartDrawer.tsx are readable as
behaviour specs (the overlay contract, the three purchase paths, the sidebar/sheet split).Routes /shop, /category/:slug (via fetchCategoryBySlug, null → your 404), /products/:slug;
dev server on 4321; a static build goes through npx @wix/cli@latest release with
site.outputDirectory pointing at the build folder, an SSR build is hosted by you. Item-page tags
from the entity’s seoData.
Read static.md in this folder: it is the whole playbook for this stack, in one file. The pages
import the modules from the site’s js/wix/ folder and write the purchase controls on the stores;
static.md gives each control as tested code, with the listing, the product page, the routes,
the rules and the seed.
Read the reference files listed above before writing any surface.
Run deploy.mjs storefront --stack static in the project folder anyway: js/wix/ is both the
browser-side code and the readable spec. Then split by where the call runs. Reads on the
server: port js/wix/catalog.ts and catalog-core.ts to your language — the same six functions
returning the same DTO shapes as dicts, one anonymous visitor token per process for these public
reads (mint and refresh per client.ts) — and render shop, category, and PDP in your templates to
the contracts above, so product names and prices are in the HTML; item-page tags from the entity’s
seoData. Buying in the browser: the cart drawer on ./js/wix/cart-store.js, the PDP’s
option picker and the tile’s quick add on ./js/wix/product-detail-store.js (pass the product’s
slug, or the rendered ProductDetail as initial in a JSON script tag), exactly as the static
wiring above — the browser owns the shopper’s visitor token, so the server never handles
per-shopper tokens. Routes stay /shop, /category/<slug>, /products/<slug>. Add your public https origin
to the OAuth app’s allowed domains before checkout can return.
Pre-rendered (Frozen-Flask, Pelican, any static-site generator) → Wix-hosted. Same port for
the reads, run at build time with one anonymous token; the generator must emit every product and
category page (a URL generator over fetchCategories() plus a full searchCatalog walk by
cursor — never only the first page). Run deploy.mjs storefront --stack static --out <build dir>
so js/wix/ is inside the output the pages import from, point site.outputDirectory at that
folder, wix release. Pages sit at different depths (/, /category/…, /products/…): give
the templates one base path to js/wix/ (a template variable, or root-relative /js/wix/…),
never a relative ./js/wix/ — it breaks one level down. The frozen grid is the first paint; the
shop’s sort, filters, facets, search, and load-more still run client-side on it through
createShopStore() from ./js/wix/shop-store.js, exactly as on a static site, so the gallery
contract above applies. Close
with the live URL, the rebuild + release command, and one line for the owner: dashboard edits to
the catalog reach the site when that command runs; cart and checkout are live regardless.
Import ./styles/global.css once at the app entry (needs @tailwindcss/vite in the vite
config plugins — deploy already added the dep). Write route wrappers in the project’s router:
/shop → your shop component; /category/:slug → the same shop component with
initialCategoryId resolved from fetchCategoryBySlug(slug) (null → your 404 view);
/products/:slug → your detail component (useProductDetail({ slug }) — components fetch
client-side when no initial is passed).
Mount the shipped CartButton in the header and CartDrawer once. Deploy wrote the public
client id into wix/config.ts; nothing else to configure.
Routes on Wix hosting: the host serves files only, so a clean route answers 404 when loaded directly — hash routes, or one HTML file per route, decided before the first route is written; any URL handed to Wix as a return target must be one the host serves (SKILL.md step 1).
wix/storefront/ for what they don’t cover (API contracts: the wix-docs skill).useProductDetail — never add a product with options by
picking variants[0], and never gate canAdd yourself. On the PDP that is the shipped
OptionPicker; in the gallery the shipped QuickAdd — a tile never adds a product with options itself, and never hides the buy path
behind “go to the product page” for a product that has no options.FilterPanel — mounted in the gallery whenever the store has a
catalog to filter; not rebuilt with fewer controls, not dropped because the brief didn’t ask./category/[slug] with its SEO block, linked from the chrome; a
category that exists only as a state toggle on /shop has no URL to share or index.@theme tokens, and your
markup uses Tailwind utilities on the same tokens — one design system across shipped and written
code. No parallel theme files, no hardcoded palette values in components. Where they don’t
(lib, static, a port): style with whatever your stack does well, on one token set of your
own; the rule that survives is the token set, not Tailwind.checkout()) — never a hand-built checkout URL.compareAtPrice, no struck price beside a range (the DTO already withholds it);
a fromPrice summary reads “From {price}”, never a made-up top of the range.cart (subtotal, discounts, fees, taxes, total — the
estimate’s) — never summed or hardcoded in the client; shipping says “calculated at checkout”,
tax too unless cart.taxes has rows or cart.pricesIncludeTax (the drawer already does).wixMetadata + loadSEOTagsServiceConfig +
<SEO.Tags>) exactly as the skeleton shows — owners edit those tags in their dashboard.notFound/loading above its useState/useEffect changes hook order between renders and
React throws. Hooks first, branches after.useShop‘s sort/filters/search and
searchCatalog run on Wix across the whole catalog before cursor paging; re-ordering the
array a hook already returned only sorts the slice you happen to have. Never sort/filter
client-side, and never raise the page limit instead of paging (hasMore/loadMore).Hand the owner these links — {siteId} is siteId in wix.config.json (the deploy JSON also prints
dashboardUrl, productsUrl, categoriesUrl); an entity id fills the placeholder from the seed result.
| page | https://manage.wix.com/dashboard/{siteId}/ + |
|---|---|
| Products | wix-stores/products |
| Edit a product | wix-stores/products/product/{productId} |
| Categories | wix-stores/categories/list |
| Inventory | wix-stores/inventory |
| Orders | ecom-platform/orders-list |
| Store settings | wix-stores/settings |
| Accept payments — connect a payment method | wix-cashier/payments |
| Upgrade the plan — online payments need premium | full URL: https://www.wix.com/upgrade/website?metaSiteId={siteId} |
Real payments need a connected payment method and a premium plan. Until both are done, a visitor who reaches hosted checkout sees “We can’t accept online payments. Contact us for help with your order.” Hand both links above in the close; don’t treat it as a code failure.
Per seed/SEED.md — a plain-data plan.json into seed-storefront.mjs, run from the project
root. Independent of the frontend work; seed a catalog that exercises the UI (≥1 product with
a color option, ≥1 on sale, an image per product) unless the brief says otherwise — and, when the
brief wants them, a ribbon, a modifier (choices or free text), an info section, a pre-order item,
per-variant prices, a category description/image: all plain plan fields (see seed/SEED.md).
A fresh Stores install carries Wix’s sample catalog (a dozen products such as “Baseball Cap” and
“Ceramic Flower Vase”), and the shop lists them next to the seeded ones. Never delete them, or
anything else on the site: the result’s preexisting[] names what the shop lists, and the closing
message says so with the Manage products link so the owner removes them there — never release a store
that sells “Baseball Cap” without telling the owner.