Skip to main content

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.
Prefer to learn from running code? The Journeybee Embed Examples repo is a live playground + copy-paste snippets for both lead forms and the partner portal — clone it, pnpm install, pnpm dev.

Quick start

Add a container element, load the embed script, and initialize your form:
<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.
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.

The init call

window.journeybee("init", formUuid, container, options);
ArgumentTypeRequiredDescription
"init"stringyesThe action.
formUuidstringyesUUID of your published form.
containerHTMLElementyesThe element the form iframe mounts into.
optionsobjectnoConfiguration (see below).

Options

OptionTypeDescription
companyNamestringOptional vanity slug used in the form URL path. Cosmetic — the form resolves by UUID. Defaults to journeybee.
prefillobjectValues to pre-populate fields with (see Prefilling).
customizationobjectTheme + layout overrides (see Styling).
onReady() => voidFires when the form has mounted and is ready.
onSuccess(data) => voidFires on a successful submission.
onError(data) => voidFires on a submission error.
onValidation(data) => voidFires on real-time field validation changes.
debugbooleanLogs 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.
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

PathValues
theme.colors.primarycolour — buttons, focus rings
theme.colors.backgroundcolour — form background
theme.colors.textcolour — body text
theme.spacing.paddingsize — padding around the form
theme.spacing.gapsize — vertical space between fields
theme.typography.fontFamilyfont stack
theme.typography.fontSize.*base, label, input sizes
theme.typography.fontWeightnormal | medium | semibold | bold
theme.borders.radiusnone | sm | md | lg | full | a size
theme.layout.maxWidthsize — caps and centres the form
layout.submitButton.textsubmit 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:
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",
  },
});
Prefill is applied to standard fields and to text / number custom fields. Empty values are ignored.

Handling submissions

Use the callbacks to react to submissions — close a modal, redirect, fire analytics, etc.
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:
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} />;
}
The form mounts into whatever container you give it, so a modal is just a container that appears on demand:
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):
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

SymptomLikely cause
Form doesn’t appearWrong/unpublished form UUID, or the container element doesn’t exist yet.
Customization has no effectA value failed validation (e.g. a colour without #, or a size without units).
Prefill doesn’t populate a fieldEmpty value, or a custom field that isn’t a text/number type.
onSuccess never firesCheck the browser console with debug: true for the postMessage traffic.