Forms SDK

Dos and Don'ts

The rules that keep SDK forms correct, distilled from real integrations

The short version: copy IDs and shapes from the binding map, let the SDK own what it owns, and verify by reading a response back.

Boot

  • Take apiBaseUrl from get_sdk_binding_map; a wrong host 404s.
  • Author every step container after the first with hidden, or they flash stacked during boot.
  • Remove preview: true and debug: true before deploying; shipped preview mode silently stores nothing.
  • One engine per page life: destroy() on teardown, never init again without it (duplicate responses).

Binding

  • Copy every questionId, stepId, option key, and answerShape from the binding map; a typo is silent data loss.
  • Set data-question-type on every choice group container, each control's value exactly the record's option key. No data-field-name on choice options.
  • Give each control of a multi-field component an explicit data-field-name with the record's exact keys (workEmailAddress, phoneNumber); the set is closed, don't guess email or phone.
  • Leave scheduler and file-upload questions unbound; their answers are written in code, and validate_form_html knows they are exempt.
  • In a framework, keep bound inputs uncontrolled and mounted; the listener attaches once at init, so no conditional rendering, key remounts, or controlled values.
  • Let next() and goToStep() switch steps instantly; don't disable the continue button for a partial save or insert async work before the step change unless the owner asked for a gate.
  • Calling submit() without a marker? Disable your own button until it resolves. With the marker, surface-busy handles it.
  • Submit button on the last question step; both endings get a data-step-id container. The SDK reveals endings itself.
  • Declare data-surface-nav="js" when navigation is code-driven; don't add fake buttons to quiet the validator.
  • Emit hidden as a constant in framework markup, never derived from state.

Data and policies

  • Ask the form owner what an invalid email means (default: annotate only); never gate navigation on validateEmail on your own initiative. Verdicts fail open on vendor outages.
  • Enforce required fields and selection limits in your page if the owner wants parity; the SDK never blocks a step.
  • Caller-computed values go in mergeMeta, visitor input in answers; answers are what workflows and tables read.
  • Check form.capabilities before promising enrichment, validation, or tracking; the record can declare features the environment does not back.

Schedulers

  • Use mountScheduler({ form, container }) and let it persist the booking; don't iframe the hosted booking page, bind the scheduler question in HTML, or hand-wire persistence unless building a custom UI on purpose.
  • Mount once as soon as form resolves, even into a hidden step container; not on step entry.
  • Drive navigation yourself for Clari; it exposes no booking signal.

Verification

  • Validate the rendered HTML with validate_form_html and fix every blocker; never a hand transcription of framework source.
  • Submit one real test response after publishing and read it back, confirming every answer and meta.surfaceTagStatus: "sdk". The read-back is the only end-to-end truth.
  • await form.flush() before a programmatic page close (test harnesses); ordinary visitors need nothing.
  • Re-run validate_form_html after every record edit and follow the editing sequence.
  • Walk your own branching logic in a real browser, both branches plus a boundary case; no Surface validator sees business logic in your code.

On this page