Issues and fixes
The SDK never fails silently and rarely throws. When something is off it
reports an issue — { path, code, message } — and keeps the page
rendering. This page lists every code by where it comes from, what it means,
and the fix.
| Where the issue comes from | Function | Reported as |
|---|---|---|
| Reading a saved document | parsePageData, validatePageData, resolveSurfaceDocument, resolveInstance | PageDataIssue |
| Rendering | InstanceView, Block | RendererIssue through onIssue (default: console.warn) |
| Assembling the theme | validateThemeDefinition | ThemeDefinitionIssue |
| Checking a manifest | validateThemeManifest, themeManifestEndpoint | ThemeManifestIssue; the endpoint throws at build |
| Editing a document | the document operations, checkPageData | DocumentIssue |
| Reading settings | resolveThemeSettings, checkThemeSettings | ThemeSettingsIssue |
path points into the value that was checked: sections[2].props.limit,
components.hero.fields[0].default, settings.pageScheme.
Reading degrades; writing refuses
A reader (rendering a page, reading settings) repairs what it can and
reports the rest: a bad value falls back to its default, an unknown section
renders nothing. A writer (an editor, a script, an agent) gets the strict
counterpart and must fix every issue before saving: checkPageData,
checkFieldValue, checkThemeSettings, and the operations that refuse.
First symptoms
Start here when something looks wrong and no code is in front of you.
| Symptom | Likely cause | Fix |
|---|---|---|
astro build fails with EnvInvalidVariables: COD_SERVER_URL is missing | A store variable declared required; the build prerenders the manifest, which loads the middleware | Declare COD_SERVER_URL, STORE_API_KEY and WOCART_PREVIEW_KEY with optional: true (quick start) |
vitest stops at startup: "There is already a server associated with the config" | @cloudflare/vite-plugin newer than 1.53.1, loaded by the Cloudflare adapter through getViteConfig | Pin it: "overrides": { "@cloudflare/vite-plugin": "1.53.1" } in package.json, then npm install (quick start) |
astro check in a render test: "Property 'cfContext' is missing … 'Locals'" | The Cloudflare adapter adds a required cfContext to App.Locals | Cast the container's locals: locals: { store } as unknown as App.Locals |
The Vercel output holds @img/sharp-*-darwin-* or other native files | Astro's default sharp image service | image: { service: passthroughImageService() }, or the media image service |
| The Cloudflare build logs "Enabling sessions with Cloudflare KV with the "SESSION" KV binding" | Sessions are on by default with the Cloudflare adapter | session: false in astro.config.mjs |
A request throws MISSING_CONFIGURATION | createStoreClient got an empty serverUrl or apiKey | Set COD_SERVER_URL and STORE_API_KEY for the running server |
The page renders the unavailable store: no name, no menu, no products, nothing to buy (config.id === "") | The config read failed: wrong URL or key, or the API is unreachable | Read the client's onError output (operation, code); a deployed server cannot reach localhost |
| Merchant text edits or saved pages never appear | The client does not pass theme | createStoreClient({ …, theme: THEME_KEY }) |
| An Arabic page shows a French label | Another language's text edits were merged | Merge only config.contentOverrides[locale] over the pack for locale |
/fr/products/x answers 404 | A middleware wired by hand ignores the rewrite | Use createStorefrontMiddleware, or return request.rewrite === null ? next() : next(request.rewrite) |
A 404 on /fr/… shows in the primary language | A middleware wired by hand resolves the store again for /404 | Use createStorefrontMiddleware, which keeps the store a request already has |
| An internal link drops the shopper's language | A raw path in an href | Pass it through the request store's href (localizeHref) |
The order form answers 403 Cross-site POST form submissions are forbidden | The page's origin differs from the URL the server sees (locally: 127.0.0.1 vs localhost) | Open the site on the origin the server reports |
Every order is refused as malformed (errorKind: "shape") | A single-product form without pricePerUnit (or a basket without items) | Post productId, productName and pricePerUnit, or items |
| A placed order shows no thank-you page | The redirect was attempted in the middleware, or the page never reads the result | Use createStorefrontMiddleware (or runFormAction); redirect from the page with Astro.getActionResult and thankYouHref |
/wocart-preview answers 404 | No WOCART_PREVIEW_KEY, the key read once when the module loaded, or the surface is not one the theme declares | Set the deployment's preview key; pass it as publicKey: () => WOCART_PREVIEW_KEY; declare the surface |
/wocart-preview answers 403 | The token is for another storefront origin or another theme key, expired, or a draft was posted cross-origin | Open the preview from the store's own Wocart Studio |
| A section's script stops working after a Studio edit | It bound once on import; a swapped section never re-runs scripts | Bind with bindEach from @wocart/theme/lifecycle |
| A style choice has no effect | The theme gives that step or width no size, or the stylesheet is not linked | Add the size to defineTheme({ style }); render <ThemeStyles> and serve wocart-styles.css |
| A color scheme change has no effect | The component hardcodes colors | Use the --wc-color-* variables of the scheme the section wears |
| The same product is requested several times per page | Several read caches, or different read keys | One createReadCache() per page, passed to every section; one key per resource |
| A field shows nothing and nothing is reported | The component reads a prop name the field does not have, or load returns the same key | Use InferProps; keep load's keys out of fields |
Reading a document
Returned by parsePageData, validatePageData and resolveSurfaceDocument,
and by resolveInstance for one instance. Only envelope-invalid makes the
document unusable; then the theme's default renders.
| Code | Meaning | What the reader does | Fix |
|---|---|---|---|
envelope-invalid | Not a JSON object; version is not 1; surface is not a surface key; sections is not an array; or the saved document is for another surface | page: null; resolveSurfaceDocument renders the theme's default | Save { version: 1, surface, sections } for the right surface |
instance-dropped | An instance lacks a string id, a kebab-case type, or has a malformed props, children, slots or fixed | Drops that instance and its tree | Give every instance a non-empty id and a valid type |
duplicate-id | An instance id appears twice (or a list item id twice in its list) | Keeps the first, drops the later one | Ids are unique in a document; use newInstanceId() |
unknown-type | No component in the catalog has this type | The instance renders nothing; the rest of the page renders | Restore the component, or remove the instance from the document |
unknown-prop | A stored prop the definition does not declare | Ignored | Remove it, or declare the field |
invalid-prop | A value fails its field's rules (wrong type, out of range, unsafe link, unknown option, bad image key, list past max) | Treated as absent: pack text or default applies | Store a value the field accepts (checkFieldValue) |
missing-required-prop | A required field has no value, no pack text and no default | Omitted; the component must not print an empty element | Give the field a value, a default or a contentKey |
undeclared-children | Children stored on a component with no default slot | Dropped | Declare a default slot, or move the children |
unknown-slot | Content in a slot the component does not declare | Dropped | Declare the slot, or move the content |
slot-not-multiple | More than one child in a multiple: false slot | Keeps the first | Remove the extras |
disallowed-child | A child whose type the slot's allow list excludes | Dropped | Allow the type, or move the child; narrowing allow breaks saved pages |
invalid-provenance | theme is not { key, version } | Ignored | Write a valid provenance or none |
theme-mismatch | The document was written for another theme key | resolveSurfaceDocument renders the default | Expected after a theme switch; the merchant re-composes the page |
unknown-fixed | A stored fixed block the definition no longer places (also reported by <Block> for an undeclared name) | Ignored at render; completePage removes it | Declare it in fixed, or remove the stored entry |
fixed-type-mismatch | A stored fixed block of another type than the definition's | Rendered as the declared type with its defaults | Store the declared type |
missing-required-block | A required section or block is absent or hidden | Added (with defaults) or shown again | Expected for old documents; completePage fixes the stored copy |
limit-exceeded | Past a cap: 50 sections, 50 per slot, 1,000 instances, 6 levels of nesting, or a surface's or slot's own max | Renders up to the cap | Split the page; respect max |
resolveSurfaceDocument also tells you why a default rendered, in its
reason:
reason | Meaning |
|---|---|
null | The saved document rendered (source: "saved") |
"absent" | The store saved nothing, or the read failed |
"invalid" | What was saved is not a page document |
"other-surface" | The saved document is for another surface |
"other-theme" | It was composed for another theme |
"no-overlap" | None of its sections is in this catalog |
Which document rendered
resolveSurfaceDocument returns source ("saved" or "default") and, for
the default, the reason:
reason | Meaning | What to do |
|---|---|---|
absent | The store saved no document for this surface | Nothing: the theme's default is the right answer |
invalid | What was saved is not a page document the SDK reads | Read issues; Studio re-saves a valid one |
other-surface | The saved document names another surface | Check the surface key the route passes |
other-theme | The saved document was composed for another theme key | Expected after a store switches theme; check your client passes theme |
no-overlap | None of the saved sections is in the catalog the route rendered with | A renamed component type, or a per-route catalog missing the section |
Rendering
InstanceView reports every reading issue above for the instance it renders,
plus three of its own:
| Code | Meaning | Fix |
|---|---|---|
missing-component | The definition's component is null or not a component (often a string left instead of an import) | Import the real .astro component. A manifestCatalog result has component: null by design — never render it |
load-failed | The definition's load threw (or a read inside it rejected) | The component still renders with its field values; handle null and [] in the component, and find the cause in the error message |
multiple-slots-unsupported | Content for more than one named slot was pushed through Astro <slot>s | Place slots with <Blocks of={blocks} slot="…">, which has no limit |
Collect them in tests with onIssue; in production the default reporter logs
[@wocart/theme/astro] <code>: <message>.
Assembling the theme
validateThemeDefinition(theme) returns [] for a sound catalog. Run it in a
test; themeManifestEndpoint runs the manifest check at build.
| Code | Meaning | Fix |
|---|---|---|
invalid-theme | The theme's key (lowercase kebab-case, ≤ 64), version (non-empty), components, locales, surfaces, areas, preview or style are malformed: duplicate keys, an unknown pageType, an alternate (product.compact) without its base, a required type placed only as a block | Correct the field the path names |
invalid-settings | settings is not a list of { key, label, fields } groups, or a group key repeats | One kebab-case key per group |
invalid-definition | A definition is not an object, has no real component, or a bad label/group | Import the component; translate the label into every declared language |
invalid-type | A catalog key or type is not lowercase kebab-case | best-sellers, not BestSellers |
type-mismatch | A definition's type differs from its catalog key | components: { "best-sellers": bestSellers } where bestSellers.type === "best-sellers" |
invalid-field | A field breaks its kind's rules: both contentKey and default; a default outside min/max/step or not in options; a slider without min, max, step; an image with a default; blocks as a key; a list item keyed id or using contentKey; a colorSchemes field on a component; a bad visibleIf | Follow the message; see Fields |
duplicate-field-key | Two fields share a key in one component, or two settings share a key across groups | Rename one |
unknown-content-key | A contentKey missing from the theme's packs (or from the contentKeys you passed) | Add the key to every pack, or fix the typo |
invalid-slot | A slot's name, allow (types must be in the catalog), max (1–50) or required is malformed, or a required type is placed only as a section | Correct the slot |
duplicate-slot | Two slots share a name | Rename one |
invalid-capability | insertable is not a boolean, or surfaces is empty or names a surface the theme does not declare | Name declared surfaces (areas as area:<key>) |
invalid-load | load is not a function | Pass an async function |
invalid-preset | A preset's key is malformed or repeated, or building it fails: an unknown type, a prop its field refuses | Fix the preset's props or children |
invalid-document | A shipped document is not a page document, is for another surface, or fails validatePageData | Fix the JSON; required props and required blocks may be left to the pack and to completion |
invalid-content | A content pack is malformed, is for a language not in locales, or a declared language has no pack | One pack per declared language, string values only |
invalid-placement | placement is not section, block or both | Use one of the three |
invalid-fixed | A fixed block's name or type is malformed or repeated, its type is placed only as a section, or fixed and required blocks contain themselves or nest more than 6 levels | Correct the fixed list |
invalid-role | role is not order-form | Use "order-form" or omit it |
order-form-not-guaranteed | A surface whose pageType is product does not always render a component with role: "order-form" | Require a section on the surface that reaches the order form through a fixed block or a slot's required (the starter's product-main requires one product-order block in its info slot, src/theme/sections/product.ts) |
Checking a manifest
validateThemeManifest(input) reads a manifest as JSON from outside the
theme. A structural failure returns manifest: null; a non-semver version is
advisory and returns the manifest with an issue.
| Code | Meaning |
|---|---|
invalid-manifest | Not an object, or a top-level field is malformed |
unsupported-contract | contractVersion is not one this SDK reads (1) |
invalid-identity | The theme key is malformed |
invalid-version | Advisory: the theme version is not semver |
invalid-surface | A surface or area is malformed |
invalid-settings | The settings are malformed |
invalid-component | A component spec is malformed |
invalid-content | The content packs are malformed |
type-mismatch | A component's type differs from its key |
non-serializable | A function, symbol, bigint, non-finite number or cycle — the manifest must be plain JSON |
themeManifestEndpoint(theme) throws [@wocart/theme] the theme manifest is invalid at build with the issues listed. Fix the definitions;
validateThemeDefinition reports the same problems with more detail.
Editing a document
The operations return { ok: false, issue } instead of a document, and
checkPageData returns every issue a document has before it is saved.
| Code | Meaning |
|---|---|
invalid-document | Not { version: 1, surface, sections }, a surface the theme does not declare, or a theme that names another theme |
invalid-instance | The instance to insert is not a valid instance |
invalid-index | The index is outside the destination list |
invalid-props | A prop is undeclared, refused by its field, a style step the theme does not size, or a required value being removed |
unknown-instance | No instance with that id (or that parent) in the document |
unknown-type | The type is not in the catalog |
unknown-slot | The parent declares no such slot (the top level has none) |
duplicate-id | An id the document already holds |
not-insertable | The component has insertable: false |
not-available | Its capabilities.surfaces do not cover this surface |
not-allowed | The slot's allow list excludes the type |
not-placeable | Its placement forbids this level (a block at the top level, a section in a slot) |
slot-full | A multiple: false slot already holds one |
fixed-block | Fixed blocks are never removed, moved, copied or hidden; or a stored fixed block is undeclared or of the wrong type |
required-block | The change would leave fewer than min or more than max of a required type |
limit-exceeded | A cap would be passed: sections, slot, instances, depth |
cycle | An instance cannot move into itself or its own descendant |
Reading settings
| Code | Meaning | Reader | Writer (checkThemeSettings) |
|---|---|---|---|
invalid-settings | The stored settings are not an object | All defaults apply | Refused |
unknown-setting | A key the theme does not declare | Ignored | Refused |
invalid-setting | A value its field refuses; a style step the theme does not size; a colorScheme naming a scheme the store does not have | The default applies | Refused |
missing-required-setting | A required setting has no value or default | Absent | — |
Preview tokens
readPreviewRequest and readRevalidateRequest answer a status and a reason
instead of a draft. A refused token's reason is one of:
| Reason | Meaning |
|---|---|
malformed | Not a three-part compact JWS |
unsupported-algorithm | Not EdDSA |
invalid-key | WOCART_PREVIEW_KEY is not an Ed25519 public JWK |
bad-signature | Not signed by the matching private key |
invalid-claims | Missing or mistyped claims, or made for another use (a revalidation token on the preview route, or the reverse) |
expired / not-yet-valid | Outside its time window (30 seconds of clock skew are allowed) |
too-long-lived | Longer than allowed: one hour for a preview, five minutes for a revalidation |
wrong-audience | Made for another storefront origin |
wrong-theme | Made for another theme key |