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

# Embedded Lead Forms

> Embed a Journeybee lead-capture form on any website with a single script tag

# Embedded Lead Forms

Drop a Journeybee lead-capture form onto any website — your marketing site, a
partner microsite, a landing page — with one `<script>` tag. The form renders in
a sandboxed iframe, auto-resizes to its content, and posts submissions straight
into Journeybee.

<Note>
  Prefer to learn from running code? The
  [**Journeybee Embed Examples**](https://github.com/Journeybeeio/embed-examples)
  repo is a live playground + copy-paste snippets for both lead forms and the
  partner portal — clone it, `pnpm install`, `pnpm dev`.
</Note>

## Quick start

Add a container element, load the embed script, and initialize your form:

```html theme={null}
<div id="jb-form"></div>

<script src="https://forms.journeybee.io/api/embed"></script>
<script>
  window.journeybee("init", "YOUR_FORM_UUID", document.getElementById("jb-form"), {
    onSuccess: (data) => console.log("submitted", data),
  });
</script>
```

That's the whole integration. The script defines a global `window.journeybee(...)`
function; calls you make before the SDK finishes downloading are queued and
replayed automatically, so the snippet above works as-is.

<Note>
  Your **form UUID** comes from the form's embed settings in Journeybee. It's a
  public value — safe to ship in client-side HTML. The form is resolved by its
  UUID alone.
</Note>

## The `init` call

```js theme={null}
window.journeybee("init", formUuid, container, options);
```

| Argument    | Type          | Required | Description                              |
| ----------- | ------------- | -------- | ---------------------------------------- |
| `"init"`    | string        | yes      | The action.                              |
| `formUuid`  | string        | yes      | UUID of your published form.             |
| `container` | `HTMLElement` | yes      | The element the form iframe mounts into. |
| `options`   | object        | no       | Configuration (see below).               |

### Options

| Option          | Type             | Description                                                                                                     |
| --------------- | ---------------- | --------------------------------------------------------------------------------------------------------------- |
| `companyName`   | string           | Optional vanity slug used in the form URL path. Cosmetic — the form resolves by UUID. Defaults to `journeybee`. |
| `prefill`       | object           | Values to pre-populate fields with (see [Prefilling](#prefilling-fields)).                                      |
| `customization` | object           | Theme + layout overrides (see [Styling](#styling-the-form)).                                                    |
| `onReady`       | `() => void`     | Fires when the form has mounted and is ready.                                                                   |
| `onSuccess`     | `(data) => void` | Fires on a successful submission.                                                                               |
| `onError`       | `(data) => void` | Fires on a submission error.                                                                                    |
| `onValidation`  | `(data) => void` | Fires on real-time field validation changes.                                                                    |
| `debug`         | boolean          | Logs the postMessage traffic to the console.                                                                    |

## Styling the form

Pass a `customization` object to theme the form. Every value is validated
server-side before it can drive any CSS, so only safe values are accepted:

* **Colours** must be hex (`#RRGGBB` / `#RGB`), `rgb(...)`, or `rgba(...)`.
* **Sizes** must include units (`px`, `rem`, `em`, `%`, `vh`, `vw`).
* **Font families** are letters, spaces, commas, and quotes only.

```js theme={null}
window.journeybee("init", "YOUR_FORM_UUID", document.getElementById("jb-form"), {
  customization: {
    theme: {
      colors: {
        primary: "#4f46e5",      // buttons, focus rings
        background: "#ffffff",   // form background
        text: "#111827",         // body text
      },
      spacing: {
        padding: "24px",         // padding around the form
        gap: "16px",             // space between fields
      },
      typography: {
        fontFamily: "'Inter', sans-serif",
        fontSize: { base: "16px", label: "14px", input: "16px" },
        fontWeight: "medium",    // normal | medium | semibold | bold
      },
      borders: {
        radius: "md",            // none | sm | md | lg | full | a size value
      },
      layout: {
        maxWidth: "480px",       // caps + centres the form
      },
    },
    layout: {
      submitButton: { text: "Get started" },
    },
  },
});
```

### Supported customization

| Path                          | Values                                             |
| ----------------------------- | -------------------------------------------------- |
| `theme.colors.primary`        | colour — buttons, focus rings                      |
| `theme.colors.background`     | colour — form background                           |
| `theme.colors.text`           | colour — body text                                 |
| `theme.spacing.padding`       | size — padding around the form                     |
| `theme.spacing.gap`           | size — vertical space between fields               |
| `theme.typography.fontFamily` | font stack                                         |
| `theme.typography.fontSize.*` | `base`, `label`, `input` sizes                     |
| `theme.typography.fontWeight` | `normal` \| `medium` \| `semibold` \| `bold`       |
| `theme.borders.radius`        | `none` \| `sm` \| `md` \| `lg` \| `full` \| a size |
| `theme.layout.maxWidth`       | size — caps and centres the form                   |
| `layout.submitButton.text`    | submit button label                                |

## Prefilling fields

Pass a `prefill` object to pre-populate fields. Standard fields are keyed by
name; custom fields are keyed by their field UUID:

```js theme={null}
window.journeybee("init", "YOUR_FORM_UUID", document.getElementById("jb-form"), {
  prefill: {
    email: "jane@acme.com",
    first_name: "Jane",
    last_name: "Smith",
    company_name: "Acme",
    phone_number: "+1 555 0100",
    partnership_uuid: "PARTNERSHIP_UUID",
    // custom field, keyed by its UUID:
    "b1d2…": "Enterprise",
  },
});
```

<Note>
  Prefill is applied to standard fields and to **text / number** custom fields.
  Empty values are ignored.
</Note>

## Handling submissions

Use the callbacks to react to submissions — close a modal, redirect, fire
analytics, etc.

```js theme={null}
window.journeybee("init", "YOUR_FORM_UUID", document.getElementById("jb-form"), {
  onReady: () => console.log("form ready"),
  onSuccess: (data) => {
    // lead captured in Journeybee
    window.location.href = "/thank-you";
  },
  onError: (data) => console.error("submission failed", data),
  debug: true,
});
```

## React

Load the loader once, initialize on mount, and tear down on unmount so you never
leak a duplicate iframe:

```tsx theme={null}
import { useEffect, useRef } from "react";

const FORM_UUID = "YOUR_FORM_UUID";
const FORMS_BASE_URL = "https://forms.journeybee.io";

export function LeadForm() {
  const ref = useRef<HTMLDivElement>(null);

  useEffect(() => {
    const SCRIPT_ID = "journeybee-forms-loader";
    let script = document.getElementById(SCRIPT_ID) as HTMLScriptElement | null;

    const init = () => {
      if (!ref.current || !window.journeybee) return;
      window.journeybee("init", FORM_UUID, ref.current, {
        customization: { theme: { colors: { primary: "#4f46e5" } } },
        onSuccess: (data) => console.log("submitted", data),
      });
    };

    if (!script) {
      script = document.createElement("script");
      script.id = SCRIPT_ID;
      script.src = `${FORMS_BASE_URL}/api/embed`;
      script.async = true;
      script.addEventListener("load", init);
      document.body.appendChild(script);
    } else if (window.journeybee) {
      init();
    }

    return () => {
      window.journeybee?.("destroy", FORM_UUID);
      if (ref.current) ref.current.innerHTML = "";
    };
  }, []);

  return <div ref={ref} />;
}
```

## Modal pattern

The form mounts into whatever container you give it, so a modal is just a
container that appears on demand:

```js theme={null}
function openFormModal() {
  document.getElementById("modal").style.display = "block";
  window.journeybee("init", "YOUR_FORM_UUID", document.getElementById("modal-body"), {
    onSuccess: () => (document.getElementById("modal").style.display = "none"),
  });
}
```

## Tearing down

Remove a mounted form (e.g. when closing a modal or unmounting a component):

```js theme={null}
window.journeybee("destroy", "YOUR_FORM_UUID");
```

To re-render with new options (a different theme or prefill), `destroy` then
`init` again.

## Security

* **Public by design.** Form UUID and company slug are safe in client HTML — the
  form is a public, unauthenticated landing surface and can be embedded on any
  origin.
* **Customization is sandboxed.** Colours, sizes, and fonts are regex-validated
  server-side; arbitrary CSS cannot be injected.
* **Spam protection** is built in: a hidden honeypot field and a minimum
  time-to-submit silently drop automated submissions.

## Troubleshooting

| Symptom                          | Likely cause                                                                    |
| -------------------------------- | ------------------------------------------------------------------------------- |
| Form doesn't appear              | Wrong/unpublished form UUID, or the container element doesn't exist yet.        |
| Customization has no effect      | A value failed validation (e.g. a colour without `#`, or a size without units). |
| Prefill doesn't populate a field | Empty value, or a custom field that isn't a text/number type.                   |
| `onSuccess` never fires          | Check the browser console with `debug: true` for the postMessage traffic.       |
