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-sdkpnpm and yarn work the same. The package is @surface-labs/forms-sdk on npm: ESM-first, with zero runtime dependencies. init returns a promise, so use target: "esnext" for top-level await, or wrap the call in an async function. You can also 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 most common 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-typeon the container, eachvalueequal to the record's option key, nodata-field-nameon 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
Ready to Get Started?
See it on your own site, or open the app and build the first form.