Website
JavaScript API
Every widget command, option, callback and browser event.
The widget's JavaScript API is a single global function, cauliflower(command, ...args), plus a window.Cauliflower object with the same methods. This page lists every command, option, callback and event. For installation and a guided setup, start with Website widget.
Calling commands
The loader snippet defines cauliflower() as a queue before embed.js arrives. Commands you call early are stored and run, in order, as soon as the script loads. After that, calls run immediately.
cauliflower("init", { workspace: "acme" });
cauliflower("identify", { email: "[email protected]" });
cauliflower("form", { router: "inbound", selector: "#demo-form" });Once embed.js has loaded, window.Cauliflower exposes the commands as methods, which is convenient when you want a return value:
const result = await window.Cauliflower.route({ router: "inbound", lead: { email: "[email protected]" } });cauliflower("route", …) returns the same promise once the script has loaded, but returns nothing while calls are still being queued. Use callbacks or events when you can't be sure the script has loaded. Unknown commands log a warning, and errors thrown by a command are logged to the console instead of breaking your page.
Commands
| Command | Arguments | Returns |
|---|---|---|
init | { workspace, host? } | Nothing |
identify | fields object | Nothing |
form | Form options | Nothing |
route | Route options | Promise of the route response |
open | { link, prefill?, ...callbacks } | Nothing |
inline | { target, link?, token?, prefill? } | The created iframe |
close | None | Nothing |
on | event, handler | Nothing |
init
Sets the workspace. Call it before any other command.
| Option | Type | Description |
|---|---|---|
workspace | string | Your workspace slug, for example acme. Required. |
host | string | Base URL of your Cauliflower server. Defaults to the origin embed.js was loaded from, so you only need it when you serve the script from somewhere else. |
identify
Stores lead fields for the rest of the page view. They're merged into every route call (values passed to route win) and used to prefill booking links opened with open and inline. Only string values are used for prefill.
cauliflower("identify", { email: "[email protected]", name: "Jane Cooper", company: "Acme" });form
Binds one or more forms to a router. On submit, the form's fields are routed and the outcome is shown to the visitor.
| Option | Type | Default | Description |
|---|---|---|---|
router | string | Required | Router slug or rtr_… id. |
selector | string | "form" | CSS selector for the forms to bind. Without selector or form, every form on the page is bound. |
form | HTMLFormElement | A form element to bind instead of a selector. | |
map | object | Copies your field names to Cauliflower's: { email: "contact_email" }. | |
submit | "prevent" or "after" | "prevent" | "after" submits the form natively once the modal closes. |
beforeRoute | function | Receives the collected fields. Return the fields (modified or not) to continue, or false to skip routing. | |
source | "widget" or "link" | "widget" | The router entry point to use. "widget" is the Website form entry point; "link" is the Router link entry point used by the hosted router page. |
| Callbacks | functions | Any of the callbacks. |
When selector matches nothing yet, the widget keeps checking for about 10 seconds so forms rendered after page load are still bound. A form is never bound twice.
route
Routes a lead you've collected yourself and shows the outcome exactly like a bound form.
| Option | Type | Description |
|---|---|---|
router | string | Router slug or rtr_… id. Required. |
lead | object | The lead's fields. An email is required. |
source | "widget" or "link" | Entry point. Defaults to "widget" (the router's Website form entry point). |
| Callbacks | functions | Any of the callbacks. |
cauliflower("route", {
router: "inbound",
lead: { email: "[email protected]", first_name: "Jane", company: "Acme", company_size: "201-1000" },
onBooked: function (meeting) {
console.log("Booked with", meeting.host.name, "at", meeting.start_time);
},
onDisqualified: function (result) {
console.log("Disqualified:", result.reason);
},
});The promise rejects when the request fails, for example when the email is missing, the router is paused or doesn't accept website submissions, the site isn't an allowed origin or the visitor hits the rate limit. The error event fires in the same cases.
open
Opens a booking link in the modal.
| Option | Type | Description |
|---|---|---|
link | string | The meeting type's slug. Required. |
prefill | object | Values for the form: name, email, phone and question keys or lead fields. Merged over identify values. |
onBooked, onClose | functions | See callbacks. |
document.querySelector("#talk-to-sales").addEventListener("click", function () {
cauliflower("open", { link: "product-demo", prefill: { email: "[email protected]" } });
});inline
Embeds a booking page inside an element on your page. The iframe fills the element's width, starts at 640px high and resizes to fit its content as the visitor moves through the steps.
| Option | Type | Description |
|---|---|---|
target | string or HTMLElement | Selector or element to render into. Its current content is replaced. Required. |
link | string | A meeting type slug. |
token | string | A router booking token, for example from a REST API routing response. Use instead of link. |
prefill | object | Prefill values, as for open. Used with link. |
<div id="calendar"></div>
<script>
cauliflower("inline", { link: "product-demo", target: "#calendar", prefill: { company: "Acme" } });
</script>Inline embeds don't take callbacks. Listen for the booked event instead.
close
Closes the open modal or message card, if any. The close event fires with reason: "user".
on
Registers a handler for a widget event. Handlers registered with on receive the event's data as their only argument.
cauliflower("on", "booked", function (meeting) {
window.dataLayer && window.dataLayer.push({ event: "meeting_booked", host: meeting.host.name });
});Callbacks
Callbacks are passed in the options of form, route and open, and only fire for that call.
| Callback | Argument | When it runs |
|---|---|---|
onRouted | Route response | The router returned a decision, whatever the outcome. Runs before anything is shown. |
onBooked | Booked meeting | The visitor booked a meeting in the modal. |
onDisqualified | Route response | The outcome is disqualified. |
onAssigned | Route response | The outcome is assigned. |
onClose | None | The modal or message card closed, for any reason. |
For a lead sent to a calendar, the order is onRouted, then onBooked if they book, then onClose.
Events
Events fire for every widget interaction on the page, no matter which command started it. Subscribe with on, or listen for the matching cauliflower:<event> browser event on window, where the data is in event.detail.
| Event | Data | Fires when |
|---|---|---|
open | { url } | A booking page opened in the modal. |
routed | Route response | A routing request succeeded, whatever the outcome. |
booked | Booked meeting | A meeting was booked in a modal or an inline embed. |
disqualified | Route response | A lead was disqualified. |
assigned | Route response | A lead was assigned an owner without a meeting. |
close | { reason } | A modal or message card closed. |
error | Error | A routing request failed. |
reason is "user" when the visitor closed it from your page (close button, backdrop click or Escape) or you called close. It's "done" when the booking page closed itself, for example with its Close button after booking, or when the widget replaced one modal with another.
window.addEventListener("cauliflower:routed", function (e) {
console.log("Outcome:", e.detail.outcome);
});
window.addEventListener("cauliflower:booked", function (e) {
console.log("Meeting", e.detail.uid, "starts", e.detail.start_time);
});Browser events are handy when another script, such as your analytics or tag manager, needs to react without touching the widget setup.
Route response
route, onRouted, onDisqualified, onAssigned and the routed, disqualified and assigned events all receive this object.
| Field | Type | Description |
|---|---|---|
outcome | string | book, assigned, disqualified, redirect, no_match or error. error means the path couldn't run, for example because nobody was available to host. |
token | string or null | Booking session token when the outcome is book. |
booking_url | string or null | The router booking page, https://cal.example.com/r/<token>. Valid for 7 days. |
embed_url | string or null | The same page with ?embed=1, which the modal loads. |
message | string or null | The calendar headline for book, or the path's message for disqualified and assigned. |
redirect_url | string or null | The URL of the path's Redirect step. The widget follows it for redirect, disqualified and assigned outcomes. Always null for book: redirects after booking are handled by the booking page. |
redirect_delay_seconds | number or null | Seconds to show the message before redirecting. |
reason | string or null | The disqualification reason, such as "Personal email". |
meeting_type | object or null | { title, duration_minutes } of the meeting type for book. |
assigned_user | object or null | { name } of the chosen rep when the router picked one person, such as the record owner or the assignee of an assign step. |
brand | object | { name, color } of your workspace. |
{
"outcome": "book",
"token": "q3Zt8kLm…",
"booking_url": "https://cal.example.com/r/q3Zt8kLm…",
"embed_url": "https://cal.example.com/r/q3Zt8kLm…?embed=1",
"message": "Pick a time with one of our specialists",
"redirect_url": null,
"redirect_delay_seconds": null,
"reason": null,
"meeting_type": { "title": "Product demo", "duration_minutes": 30 },
"assigned_user": null,
"brand": { "name": "Acme", "color": "#1f8a5b" }
}Booked meeting
onBooked and the booked event receive the meeting as the booking page saw it:
| Field | Description |
|---|---|
uid | The booking's public id, also used in the manage URL. |
title | Meeting type name. |
start_time, end_time | ISO 8601 timestamps. |
timezone | The invitee's time zone. |
host | { name } of the host. |
meeting_url | Video link, if any. |
location | Location text, such as "Google Meet" or an address. |
manage_url | https://cal.example.com/booking/<uid>, where the invitee can reschedule or cancel when the meeting type allows it. |
can_reschedule, can_cancel | Whether the meeting type lets the invitee reschedule or cancel the booking themselves. |
confirmation_message | The meeting type's confirmation message, or null. |
Data attributes
For simple pages you can skip JavaScript entirely:
| Attribute | On | Effect |
|---|---|---|
data-workspace | The embed.js script tag | Sets the workspace without calling init. |
data-cauliflower-router | A form | Binds the form to this router slug, like form. |
data-cauliflower-submit="after" | A form with data-cauliflower-router | Submits the form natively after the modal closes. |
data-cauliflower-link | Any element | Opens this booking link in the modal on click, like open. |
<script src="https://cal.example.com/embed.js" data-workspace="acme" async></script>
<form data-cauliflower-router="inbound" data-cauliflower-submit="after" action="/thanks" method="post">
<input name="email" type="email" required />
<button type="submit">Request a demo</button>
</form>
<a href="#" data-cauliflower-link="product-demo">Book a call</a>data-workspace only works on a plain script tag like the one above, not with the loader snippet. A plain script tag doesn't define the cauliflower() queue, so use the loader snippet if you also call commands. Attributes are read once when the page loads; bind elements added later with form or open.