Troubleshooting
Symptom to cause to check: the SDK form debugging runbook
Debugging an SDK form triangulates three views: the browser console (runtime warnings), validate_form_html (the static gate), and response read-back (what actually landed). When in doubt, read a response back.
First moves
-
form.state()in the console:currentStepId,answers,responseId,finished.form.capabilitiesshows what is wired. -
Attach a firehose while reproducing:
["viewed", "started", "stepChanged", "stepCompleted", "completed", "disqualified", "saved", "error"] .forEach((e) => form.on(e, (p) => console.log("[surface]", e, p))); -
Re-run
validate_form_htmlagainst the deployed HTML; drift since the last validation is the most common root cause after a record edit.
Symptom table
| Symptom | Likely cause | Check |
|---|---|---|
init throws "could not fetch runtime config" | Wrong or missing apiBaseUrl | The error names the URL it tried; use the one from get_sdk_binding_map |
| No responses at all | preview: true left in the deployed page | Grep the page for preview; saved never fires in preview |
| No responses at all | Form never published, so IDs don't match | Console shows unbound-question warnings; run publish_form |
| Some answers missing | Binding drift: a question ID unbound or misspelled | Console names the unbound IDs; validate_form_html |
| Some answers missing | Partial responses off and the visitor abandoned mid-form | trackPartialResponses in settings; expected |
| 400 "Fields are missing or incorrectly formatted" | Hand-built setAnswer with the wrong shape | Compare with the binding map's answerShape |
| Choice answers wrong or empty | value attributes drifted from the record's option keys | validate_form_html warns on exactly this |
| Same email accepted one day, rejected the next | A fail-open verdict later resolving definitively; expected | Check failed on the verdict |
| Thank-you never shows | No thank-you ending step, or no container for it | A step with endStepKind: "thank_you" and its container |
| Disqualified visitors see the thank-you | No disqualified ending step on the record | disqualify() only reveals a "disqualified" ending |
| Steps don't switch | data-step-id values don't match record step IDs, or invalid goto | Console warns on invalid goToStep |
| Duplicate responses per visitor | init called again without destroy() (SPA remounts) | One engine per page life |
| GTM, GA4, or Meta events not firing | Preview mode, or settings not set | Deployed non-preview page; window.dataLayer after a step submit |
| Ad conversions not firing | Rules gate on trigger and URL conditions; terminal rules fire on completion only; queued until first responseId | Rules in the dashboard; watch for saved first |
| Enrichment fields empty | No enabled enrichment provider | form.capabilities.enrichment; debug: true warns at boot |
| Scheduler says "No availability" | The event type has no bookable slots | list_schedulers with includeAvailability: true |
| Lead journey empty | Journey ID never carried (or journey: false) | Save payload's meta for userJourneyId |
| Lead attribution missing | Identify blocked (preview, ad blocker), or stale SPA session | form.identify(); network tab for the identify call |
| Slower than the hosted form | A custom header or JSON content type added preflights | DevTools should show no OPTIONS requests |
| Finished response stored as partial | Page torn down mid-flight | await form.submit(), then await form.flush() before programmatic close |
Reading a response back
list_responses (newest first), then get_response. Confirm: every expected question, structured choice answers, surfaceTagStatus: "sdk", and, for a booking step, eventScheduled: true. A response that looks right here ends the client-side investigation.
When the record and the HTML disagree
get_sdk_binding_map is the arbiter: the source: "live" map is what the deployed SDK actually fetched; the draft map (default) is what the next publish makes true. A page validated against the draft but running against an older live record explains most "validator says fine, runtime disagrees" reports. Publish, or validate against source: "live".
Still stuck? The npm package ships a longer runbook at skills/surface-forms-sdk/debugging.md, and debug: true narrates everything the SDK does.