> ## Documentation Index
> Fetch the complete documentation index at: https://developer.suki.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Build a Form Filling Session with EHR Handoff

> Use-case tutorial: Open a Form filling SDK session in React and map structured_data to your EHR with correlation_id

<Callout icon="info-circle" color="#FFE148">
  **What you will build**

  <p className="doc-tutorial-meta" aria-label="20 min, Intermediate">
    <Badge size="sm" color="gray" icon="clock">20 min</Badge> |
    <Badge size="sm" color="gray" icon="user-graduate">Intermediate</Badge>
  </p>

  * A Form filling session opened with template IDs
  * A stable `correlation_id` for your EHR mapping
  * Mapping `structured_data` into one EHR payload after submit
</Callout>

<Tip>
  **Using an AI coding tool?**

  Copy the following prompt to add the Suki developer documentation as a skill and [MCP server](/documentation/references/mcp) to your tool for better AI-assisted coding during the integration process. For all AI options (contextual menu, `llms.txt`, and `skill.md`), refer to [AI-optimized documentation](/documentation/references/ai-optimized-documentation).
</Tip>

<Prompt description="Install the Suki developer docs as a skill to get context on Suki's developer tools, APIs, and SDKs. Add the [MCP server](/documentation/references/mcp) for documentation search." icon="gear" iconType="regular" actions={["copy", "cursor"]}>
  Install the Suki developer docs as skill to get context on Suki's developer tools, APIs, and SDKs.

  npx skills add [https://developer.suki.ai](https://developer.suki.ai)

  Then add the Suki developer docs MCP server for access to documentation search. Follow the MCP instructions at [https://developer.suki.ai/documentation/references/mcp](https://developer.suki.ai/documentation/references/mcp).
</Prompt>

In this tutorial, you open Form filling from React, pass template IDs and a stable <strong><code>correlation\_id</code></strong>, then map <strong><code>structured\_data</code></strong> from the submit result for EHR handoff.

By the end of this tutorial, submit returns filled form values and your mapping builds one EHR payload per filled template.

<span id="prerequisites" />

## Prerequisites

Before you begin, make sure you have:

* Completed [Partner onboarding](/documentation/get-started/partner-onboarding).
* Valid Form filling <code>template\_id</code> values from Suki support. Refer to [Form filling templates](/documentation/concepts/form-filling/form-filling-templates).
* A React 18+ app that meets [Form filling SDK prerequisites](/form-filling-sdk/prerequisites).

<Note>
  EHR field names differ by partner system. The mapping in this tutorial is illustrative. Adapt <code>fields</code> to your chart schema.
</Note>

<span id="architecture" />

## Architecture

```mermaid actions={false} theme={"theme":{"light":"github-dark","dark":"material-theme-darker"}}
%%{init: {'theme':'base', 'themeVariables': { 'primaryColor':'#FFE148','primaryTextColor':'#111827','primaryBorderColor':'#D4A017','lineColor':'#6F5410','secondaryColor':'#FFF394','tertiaryColor':'#FFFADE','tertiaryTextColor':'#111827','tertiaryBorderColor':'#D4A017','arrowheadColor':'#6F5410','fontSize':'14px','edgeLabelBackground':'#FFFADE'}}}%%
flowchart LR
  A[FormFillingClient] --> B[Open session]
  B --> C[onSubmit result]
  C --> D[Map structured_data]
  D --> E[EHR-shaped payload]
```

<span id="project-setup" />

## Project setup

1. Complete Partner onboarding and obtain valid Form filling `template_id` values (see Prerequisites).
2. Install the required dependencies:

```shell theme={"theme":{"light":"github-dark","dark":"material-theme-darker"}}
# pnpm
pnpm add @suki-sdk/form-filling @suki-sdk/form-filling-react @suki-sdk/core

# npm
npm install @suki-sdk/form-filling @suki-sdk/form-filling-react @suki-sdk/core
```

3. Configure your credentials:

   <Callout icon="gear" color="#929292">
     * Keep credentials ready for the Form filling client configuration used in later steps.
     * Have valid `template_id` values from Suki for your partner account.
   </Callout>

4. Give the Form filling container height. React mounts the UI in **`.suki-form-filling`**. Without height, the iframe looks blank:

```css theme={"theme":{"light":"github-dark","dark":"material-theme-darker"}}
.suki-form-filling {
  width: 100%;
  height: min(80vh, 720px);
  min-height: 400px;
}
```

<span id="tutorial-steps" />

## Tutorial steps

Work through each step in order. Read the explanation, review the code example, then continue to the next step. The full code example is available in the accordion below.

<Steps>
  <Step title="Create FormFillingClient Once">
    Build `SukiAuthManager` and `FormFillingClient` in `useMemo`. Set `autoRegister: false` unless your integration uses provider auto-registration (default is `true`).

    ```tsx theme={"theme":{"light":"github-dark","dark":"material-theme-darker"}}
    import { useMemo } from "react";
    import { SukiAuthManager } from "@suki-sdk/core";
    import { FormFillingClient } from "@suki-sdk/form-filling-react";

    function useEncounterFormClient() {
    return useMemo(() => {
    const authManager = new SukiAuthManager({
      partnerId: "YOUR_PARTNER_ID",
      partnerToken: "YOUR_PARTNER_TOKEN",
      environment: "staging",
      loginOnInitialize: true,
      autoRegister: false,
      providerId: "YOUR_PROVIDER_ID", // Replace with your provider ID
      providerName: "Dr. Jane Smith", // Example only — replace with the provider's full name
    });
    return new FormFillingClient({ authManager });
    }, []);
    }
    ```
  </Step>

  <Step title="Open Form Filling with Template IDs and correlation_id">
    Wrap with `FormFillingProvider`. Pass template IDs from Suki support and your encounter id as `correlation_id` so results map back to the chart. Style `.suki-form-filling` with height before the UI mounts.

    ```tsx theme={"theme":{"light":"github-dark","dark":"material-theme-darker"}}
    import { useState } from "react";
    import { FormFillingProvider, FormFilling } from "@suki-sdk/form-filling-react";

    // Required: .suki-form-filling needs explicit height or the iframe looks blank.
    // .suki-form-filling { width: 100%; height: min(80vh, 720px); min-height: 400px; }

    function EncounterFormPanel({ client, encounterId, onSubmit }) {
    const [open, setOpen] = useState(false);

    return (
    <FormFillingProvider client={client}>
      <button type="button" onClick={() => setOpen(true)} disabled={open}>
        Start
      </button>
      {open && (
        <FormFilling
          form_template_ids={["YOUR_TEMPLATE_ID"]} // Example placeholder — replace with your template ID
          correlation_id={encounterId}
          onSubmit={onSubmit}
          onCancel={() => setOpen(false)}
        />
      )}
    </FormFillingProvider>
    );
    }
    ```
  </Step>

  <Step title="Map structured_data for EHR Handoff">
    In `onSubmit`, map `structured_data.generated_values` by `form_template_id`. Use `correlation_id` as your encounter key and `ambient_session_id` for idempotent saves. For production persistence, also handle your partner webhook.

    ```tsx theme={"theme":{"light":"github-dark","dark":"material-theme-darker"}}
    function mapResultToEhr(result) {
    return result.structured_data.generated_values.map((instance) => ({
    encounterId: result.correlation_id ?? "",
    sessionId: result.ambient_session_id,
    templateId: instance.form_template_id,
    fields: instance.data,
    }));
    }

    async function handleSubmit(result, setOpen) {
    const missing = result.structured_data.non_generated_values.map(
    (v) => v.form_template_id,
    );
    if (missing.length > 0) {
    console.warn("No output for templates:", missing);
    }

    const payloads = mapResultToEhr(result);
    for (const payload of payloads) {
    await fetch("/api/ehr/forms", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify(payload),
    });
    }
    setOpen(false);
    }
    ```
  </Step>
</Steps>

<span id="full-example" />

## Full code example

<div className="doc-tutorial-full-code" data-doc-tutorial-full-code data-active-lang="react">
  <Columns cols={1}>
    <Tile href="#full-example-react">
      <span className="doc-tutorial-tile-lang-icon" data-icon="react" aria-hidden="true" />
    </Tile>
  </Columns>

  <div id="full-example-react" className="doc-tutorial-lang-panel is-active" data-lang-panel="react">
    ```tsx React theme={"theme":{"light":"github-dark","dark":"material-theme-darker"}}
    // Form filling SDK + EHR handoff tutorial (React)
    // Copy this file into a React app, replace YOUR_* placeholders, then render <App />.
    // Flow: FormFillingClient → FormFillingProvider → FormFilling → map structured_data → your EHR API
    // Do not rely on browser onSubmit alone in production. Also register a partner webhook.

    import { useMemo, useState } from "react";
    import { SukiAuthManager } from "@suki-sdk/core";
    import {
      FormFillingClient,
      FormFillingProvider,
      FormFilling,
      useFormFilling,
    } from "@suki-sdk/form-filling-react";

    // Optional: sibling status under the same provider.
    // Prefer isSessionActive; the provider may keep the last error until the next form-filling:error.
    function FormFillingStatus() {
      const { isSessionActive, error } = useFormFilling();
      if (isSessionActive) {
        return <p>Form session active: speak to fill fields.</p>;
      }
      if (error) {
        return (
          <p role="alert">
            Form filling error ({error.code}): {error.message}
          </p>
        );
      }
      return null;
    }

    // Map each filled template to an EHR-shaped payload. Adapt `fields` to your chart schema.
    function mapResultToEhr(result) {
      return result.structured_data.generated_values.map((instance) => ({
        encounterId: result.correlation_id ?? "", // Your encounter key from correlation_id
        sessionId: result.ambient_session_id, // Use for idempotent saves
        templateId: instance.form_template_id,
        fields: instance.data,
      }));
    }

    function EncounterFormFilling({ encounterId }) {
      // 1) Build auth + client once for the encounter page.
      const client = useMemo(() => {
        const authManager = new SukiAuthManager({
          partnerId: "YOUR_PARTNER_ID", // Required: from partner onboarding
          partnerToken: "YOUR_PARTNER_TOKEN", // Required
          environment: "staging",
          loginOnInitialize: true,
          autoRegister: false, // Optional (default true); when true, providerName and providerOrgId are often required
          providerId: "YOUR_PROVIDER_ID", // Replace with your provider ID
          providerName: "Dr. Jane Smith", // Example only — replace with the provider's full name
        });
        return new FormFillingClient({ authManager });
      }, []);

      const [open, setOpen] = useState(false);

      // 2) Persist mapped results. Reuse this handler for onSubmit and onBackgroundSubmit.
      async function handleSubmit(result) {
        const missing = result.structured_data.non_generated_values.map(
          (v) => v.form_template_id,
        );
        if (missing.length > 0) {
          console.warn("No output for templates:", missing);
        }

        const payloads = mapResultToEhr(result);
        for (const payload of payloads) {
          // Replace with your EHR / charting API
          // Example EHR endpoint only — replace with your charting / EHR API
          await fetch("/api/ehr/forms", {
            method: "POST",
            headers: { "Content-Type": "application/json" },
            body: JSON.stringify(payload),
          });
        }
        setOpen(false);
      }

      return (
        <FormFillingProvider client={client}>
          <h1>Encounter {encounterId}</h1>
          <FormFillingStatus />
          <button type="button" onClick={() => setOpen(true)} disabled={open}>
            Start Form filling
          </button>
          {/* 3) Mount Form filling only while the session should be open.
              Style .suki-form-filling with height first or the iframe looks blank. */}
          {open && (
            <FormFilling
              form_template_ids={["YOUR_TEMPLATE_ID_A", "YOUR_TEMPLATE_ID_B"]} // Example placeholders — replace with template IDs from Suki
              correlation_id={encounterId} // Ties results back to this encounter
              onSubmit={handleSubmit} // Required: receives FormFillingResult
              onCancel={() => setOpen(false)}
              onBackgroundSubmit={handleSubmit} // Late results after the clinician reopens or leaves
            />
          )}
        </FormFillingProvider>
      );
    }

    // 4) Demo root with a sample encounter id so this file is paste-ready.
    function App() {
      // Example encounter id only — replace with your real encounter / chart key
      return <EncounterFormFilling encounterId="encounter-123" />;
    }

    export default App;
    ```
  </div>
</div>

<span id="troubleshooting" />

## Troubleshooting

<AccordionGroup>
  <Accordion title="Blank Form Filling UI">
    Give **`.suki-form-filling`** explicit height before the UI mounts. Zero height is the most common cause of a blank iframe. See [Form filling React integration](/form-filling-sdk/react-integration/react).
  </Accordion>

  <Accordion title="Empty or Wrong Templates">
    Use `template_id` values from Suki for your partner account.
  </Accordion>

  <Accordion title="Cannot Correlate Results">
    Pass a stable `correlation_id` when you open the session and reuse it in your mapping.
  </Accordion>

  <Accordion title="Production Saves">
    Prefer your partner webhook in production; this tutorial maps from `onSubmit` for the session flow.
  </Accordion>
</AccordionGroup>

<span id="summary" />

## Summary

In this tutorial, you:

* Opened Form filling with template IDs and a stable `correlation_id`.
* Received `FormFillingResult` on submit.
* Mapped `structured_data` into EHR handoff payloads.

<span id="other-tutorials" />

## Other tutorials

Continue with another end-to-end tutorial.

<div className="hp-io-method-grid tut-hub-card-grid">
  <a className="hp-io-method-card tut-hub-method-card" href="/documentation/tutorials/form-filling-websocket-code-example">
    <div className="tut-hub-card-media" aria-hidden="true" />

    <div className="hp-io-method-card-body">
      <span className="hp-wn-badge hp-wn-badge-new">Form Filling</span>
      <h3 className="hp-io-method-card-title">Build a Form Filling Session Client</h3>

      <p className="hp-io-method-card-desc">
        Create a Form filling session, send template context, stream audio, and retrieve structured form data.
      </p>

      <div className="hp-io-method-card-meta tut-hub-card-foot" aria-label="20 min, Intermediate">
        <div className="tut-hub-card-foot-meta">
          <span className="hp-io-method-card-meta-time">20 min</span>
          <span className="tut-hub-level">Intermediate</span>
        </div>
      </div>
    </div>
  </a>

  <a className="hp-io-method-card tut-hub-method-card" href="/documentation/tutorials/webhook-notification-receiver">
    <div className="tut-hub-card-media tut-hub-card-media--blue" aria-hidden="true" />

    <div className="hp-io-method-card-body">
      <span className="hp-wn-badge hp-wn-badge-new">APIs</span>
      <h3 className="hp-io-method-card-title">Build a Webhook Notification Receiver</h3>

      <p className="hp-io-method-card-desc">
        Verify HMAC signatures, parse partner notifications, and handle success and failure events.
      </p>

      <div className="hp-io-method-card-meta tut-hub-card-foot" aria-label="10 min, Beginner">
        <div className="tut-hub-card-foot-meta">
          <span className="hp-io-method-card-meta-time">10 min</span>
          <span className="tut-hub-level">Beginner</span>
        </div>
      </div>
    </div>
  </a>
</div>
