# Building with AI



The Forms SDK was designed to be authored by coding agents. The [Surface MCP server](/docs/mcp-server/overview) creates the record, hands the agent the binding contract, and statically validates the HTML. Run the loop yourself, or hand it to an agent with a prompt like "build our demo booking form as an SDK form on our landing page".

## The authoring loop [#the-authoring-loop]

<Steps>
  <Step title="Plan (optional)">
    `plan_form` with `sdk: true` critiques a blueprint before anything is created.
  </Step>

  <Step title="Create the record">
    `create_form` with `sdk: true`: steps, questions, endings; no styling or routing parameters. Give every question real question text (the responses table reads those labels). Save the `formId` and `previewToken`.
  </Step>

  <Step title="Get the binding map">
    `get_sdk_binding_map` returns, per question: `questionId`, `componentType`, `fieldNames`, option keys, `answerShape`, plus `environmentId`, `apiBaseUrl`, and a ready-to-paste `init` snippet. Use the snippet; a wrong `apiBaseUrl` is the most common failure.
  </Step>

  <Step title="Author the page">
    Write HTML or React against the [binding contract](/docs/forms-sdk/binding), copying IDs and option keys from the binding map.
  </Step>

  <Step title="Validate what ships">
    `validate_form_html` checks the markup against the record. Fix every blocker; warnings are silent data loss at runtime.
  </Step>

  <Step title="Preview, publish, deploy">
    Boot with `preview: true` and the `previewToken` to walk the draft with zero writes, then `publish_form` and deploy.
  </Step>

  <Step title="Verify end to end">
    Submit one real test response, `await form.flush()`, read it back with `get_response`, confirm every answer and `meta.surfaceTagStatus: "sdk"`. Without a browser, run headless in [Node](/docs/forms-sdk/headless-and-react#running-in-node).
  </Step>
</Steps>

## Validating framework pages [#validating-framework-pages]

`validate_form_html` must see the markup the visitor actually receives. Plain HTML: pass the file. Framework page: fetch the rendered HTML (`curl http://localhost:3000/your-page`) and pass it with `containerSelector` set to your boot container. Never validate a hand transcription of the source. If navigation or submit is code-driven, declare `data-surface-nav="js"`.

## Editing an existing SDK form [#editing-an-existing-sdk-form]

Every record edit must be followed through to the HTML:

1. `get_form_structure` (with `include: ["params"]`) for the structure and its `revision`.
2. Edit with `add_step`, `update_step`, `add_component`, `update_component`, `remove_component`, or `batch_edit`, passing the revision as `ifRevision` so concurrent edits fail loudly.
3. Re-fetch `get_sdk_binding_map` and update the HTML for every difference.
4. `validate_form_html` until clean, then `publish_form` and deploy together.

If publish and deploy cannot land simultaneously: publish first for additions, deploy first for removals and option-key changes. The dashboard's SDK form builder edits the same draft by hand.

## Tools that refuse on SDK forms [#tools-that-refuse-on-sdk-forms]

Styling, marketing, layout, and routing tools reject SDK forms: presentation lives in your HTML, navigation in your code. `simulate_response` does not apply either. `validate_form` checks configuration integrity only; a pass says nothing about your page.

## Installing the skill [#installing-the-skill]

If your agent runner supports skills (Claude Code does):

```bash
cp -r node_modules/@surface-labs/forms-sdk/skills/surface-forms-sdk .claude/skills/
```

The skill includes the workflow, HTML patterns, an editing sync loop, and a debugging runbook.
