Forms SDK

Quickstart

Install the Forms SDK, bind a form record to your own HTML, and store your first response

You need a Surface environment and an SDK form record. Create one in the dashboard (Forms, Create New Form, Build SDK form) or through the MCP server.

1. Install

npm install @surface-labs/forms-sdk

pnpm and yarn work the same. ESM-first, zero runtime dependencies; init returns a promise, so use target: "esnext" for top-level await or wrap the call in an async function. Alternatively, self-host the prebuilt IIFE bundle and use the global SurfaceFormsSDK.

2. Get your IDs

init needs environmentId, formId, and apiBaseUrl. The get_sdk_binding_map MCP tool returns all three plus a ready-to-paste init snippet and every question and step ID; the form's page in the dashboard shows the same IDs.

A wrong apiBaseUrl is the number one boot failure. The default is https://forms.withsurface.com; any other deployment 404s until you pass its host. If init throws, the error names the URL it tried.

3. Write the HTML

One container per step, one data-question-id per question:

<div id="form" hidden>
  <section data-step-id="step_about">
    <label>
      Work email
      <input type="email" data-question-id="q_email" />
    </label>
    <button type="button" class="surface-next-button">Continue</button>
  </section>

  <section data-step-id="step_role" hidden>
    <div data-question-id="q_role" data-question-type="MultipleOptionsQuestion">
      <label><input type="radio" name="role" value="Sales Ops" /> Sales Ops</label>
      <label><input type="radio" name="role" value="Leadership" /> Leadership</label>
    </div>
    <button type="button" class="surface-submit-button">Submit</button>
  </section>

  <section data-step-id="step_thanks" hidden><h2>Thanks!</h2></section>
</div>
  • Every step container after the first is authored hidden; otherwise steps flash stacked during boot.
  • The choice question binds as a group: data-question-type on the container, each value equal to the record's option key, no data-field-name on options.
  • The submit button sits on the last question step; the SDK reveals the thank-you step itself.

Full attribute vocabulary: binding contract.

4. Boot the SDK

import { SurfaceForms } from "@surface-labs/forms-sdk";

const form = await SurfaceForms.init({
  environmentId: "env_xxx",
  formId: "form_xxx",
  apiBaseUrl: "https://forms.withsurface.com",
  container: document.querySelector("#form"),
});
document.querySelector("#form").hidden = false;

With the IIFE bundle, call SurfaceFormsSDK.SurfaceForms.init(...) the same way.

During development, add debug: true to log every captured answer, lifecycle event, and navigation intent. Remove before shipping.

5. Verify

Submit a test response, then check the Responses table (or get_response over MCP). SDK responses carry surfaceTagStatus: "sdk" in their metadata.

If an answer is missing, the browser console warns about unbound record questions. See troubleshooting for the full symptom table.

Next steps

On this page