> ## Documentation Index
> Fetch the complete documentation index at: https://docs.os.default.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Build a reliable Forms SDK flow

> Initialize early, start pre-enrichment before submission, handle every result, and secure scheduler messages.

Use this guide when your page owns form submission or does not use a native `<form>` element.

After you apply this pattern, your integration starts identity work early and handles every workflow
result safely.

<Note>
  Use the automatic Pixel when your page has a standard HTML form and your code does not control
  submission.
</Note>

## Before you begin

Complete the [pictured setup guide](/pixel-sdk-setup) first. You need:

* An approved form with mapped fields.
* The public key and Pixel form UUID from the form's **Install** tab.
* An HTML ID for the form on your page.
* A test page and synthetic visitor data.

## Complete example

```html theme={null}
<head>
	<script>
		window.__defaultPixel__ = {
			key: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890',
			schedulerOrigins: ['https://book.default.com'],
		}
	</script>
	<script defer src="https://pixel.default.com/sdk.js"></script>
</head>

<body>
	<form id="request-demo">
		<label>
			Work email
			<input id="work-email" name="email" type="email" required />
		</label>
		<label>
			First name
			<input id="first-name" name="first_name" required />
		</label>
		<button type="submit">Request a demo</button>
		<p id="form-status" role="status"></p>
	</form>

	<script>
		window.addEventListener('load', () => {
			const form = document.getElementById('request-demo')
			const email = document.getElementById('work-email')
			const firstName = document.getElementById('first-name')
			const status = document.getElementById('form-status')
			const submitButton = form.querySelector('button[type="submit"]')
			let submissionPending = false

			email.addEventListener('blur', () => {
				if (!email.validity.valid) return

				DefaultPixelSDK.recordFormInteraction({
					formId: form.id,
					fieldName: email.name,
					fieldValue: email.value,
				})
			})

			form.addEventListener('submit', async (event) => {
				event.preventDefault()
				if (submissionPending) return

				submissionPending = true
				submitButton.disabled = true
				status.textContent = 'Sending…'

				try {
					const result = await DefaultPixelSDK.submitForm({
						pixelFormId: '11111111-1111-4111-8111-111111111111',
						formId: form.id,
						responses: {
							email: email.value,
							first_name: firstName.value,
						},
					})

					if (!result.success) {
						status.textContent = 'We could not send your request. Please try again.'
						return
					}

					if (!result.scheduler && !result.redirect) {
						status.textContent = 'Thank you. We will be in touch.'
					}
				} finally {
					submissionPending = false
					submitButton.disabled = false
				}
			})
		})
	</script>
</body>
```

The default `submitForm()` behavior shows a returned scheduler in a modal. A redirect handoff always
navigates the browser. The success message above appears only when neither handoff exists.

## Use the correct form identifiers

The two identifiers serve different purposes.

| Value         | Meaning                                             | Where to use it                                                  |
| ------------- | --------------------------------------------------- | ---------------------------------------------------------------- |
| `formId`      | The HTML `id` on your page, such as `request-demo`  | `recordFormInteraction({ formId })` and `submitForm({ formId })` |
| `pixelFormId` | The UUID Default assigns after you approve the form | `submitForm({ pixelFormId })` only                               |

<Warning>
  Do not pass the Pixel form UUID to `recordFormInteraction({formId})`. The warmup and submission
  then use different cache keys, so the submission cannot reuse the early result.
</Warning>

## Initialize before the visitor uses the form

Set `window.__defaultPixel__` before `sdk.js` loads. The SDK initializes when the script loads.

Do not create the script or call `init()` inside the submit handler. Late initialization misses the
email blur, delays identity work, and can delay the scheduler handoff.

If your app supplies the key at runtime, call `DefaultPixelSDK.init()` once when your page or shared
layout starts. Reuse that initialization promise across form components.

## Start pre-enrichment after a valid email

Call `recordFormInteraction()` when the visitor leaves a valid email field. This starts identity
and company lookup before submission.

Treat this call as best-effort. Do not block the form if it fails. Avoid sending the same unchanged
email repeatedly.

The SDK waits for this early lookup when a matching submission begins. It uses the saved result
once. It ignores a saved result when its email differs from the submitted email.

## Submit once and handle every result

Disable the submit button while the promise is pending. This prevents duplicate requests from quick
clicks.

`submitForm()` resolves with `success: false` for request failures. Check the result instead of
depending on a thrown error.

* Keep the default scheduler modal unless your page needs another display.
* Use `{ target: "#meeting-section" }` for an inline scheduler.
* Use `false` only when your code will handle `result.scheduler.url`.
* Expect redirect handoffs to navigate immediately. `autoDisplayScheduler` does not stop them.

## Trust scheduler messages only from your scheduler

Set `schedulerOrigins` to the scheduler origins your page uses. The SDK drops messages from origins
outside that list.

Do not include broad or unrelated origins. Include each exact origin, including the scheme.

## Exclude pages and forms from capture

Add this meta tag to disable form capture for a page:

```html theme={null}
<meta name="default:pixel" content="noform" />
```

Add `data-default-ignore` to a form or one of its parent elements to skip only that form:

```html theme={null}
<form id="internal-search" data-default-ignore>…</form>
```

The Pixel also skips paths containing `/login`, `/signin`, `/account`, `/checkout`, `/oauth`, and
paths under `/admin/`.

## Verify the result

Use the form editor's **Test** tab with synthetic data. Confirm these results:

1. Pre-enrichment starts after email blur.
2. The submission returns `success: true`.
3. The correct Form Submission workflow runs after it is published.
4. The scheduler opens in the selected display mode.
5. Failure states leave the visitor with a clear next action.

## Troubleshooting

| Problem                                     | What to check                                                                             |
| ------------------------------------------- | ----------------------------------------------------------------------------------------- |
| Pre-enrichment starts only after submission | Initialize the SDK on page load. Call `recordFormInteraction()` after a valid email blur. |
| The early result is not reused              | Pass the same HTML `formId` to `recordFormInteraction()` and `submitForm()`.              |
| The form sends duplicate requests           | Disable the submit button while `submitForm()` is pending.                                |
| The scheduler does not appear               | Check `result.scheduler`, `autoDisplayScheduler`, and the configured target element.      |
| Scheduler events do not arrive              | Add each exact scheduler origin, including `https://`, to `schedulerOrigins`.             |

## Next steps

<CardGroup cols={2}>
  <Card title="Understand the full integration" icon="code" href="/pixel-sdk-integration-guide">
    Learn how identity, form capture, workflows, and scheduler handoffs work together.
  </Card>

  <Card title="Use the API reference" icon="sliders" href="/pixel-sdk-reference">
    Find every configuration option, method, return value, and scheduler event.
  </Card>
</CardGroup>
