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);
| 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). |
customization | object | Theme + layout overrides (see Styling). |
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. |
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
| 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:
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} />;
}
Modal pattern
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
| 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. |