Embed script
embed.js puts the hosted booking page inside your site. It renders the page in an iframe, grows the frame to fit its content, and tells your code when a booking goes through. No API key, no build step: it works anywhere you can paste HTML.
You need the page's link, username/slug. For www.clndr.pro/ada/intro-call that's ada/intro-call.
Inline
<div id="booking"></div>
<script>
function mountBooking() {
Clndr.inline({
elementOrSelector: '#booking',
link: 'ada/intro-call',
config: { theme: 'auto' },
});
}
</script>
<script src="https://www.clndr.pro/embed.js" async onload="mountBooking()"></script>Call Clndr from the script's onload, as above. A plain <script> placed after an async one can run first, before window.Clndr exists, and throw Clndr is not defined.
Load the script from https://www.clndr.pro, with the www. The script builds the iframe's URL from its own origin and only trusts messages from that origin. Loaded from clndr.pro, the frame ends up on www.clndr.pro after a redirect, its messages are ignored, and the frame never resizes or reports bookings.
Modal
Clndr.modal() opens the booking page in an overlay straight away, so call it from a click:
<button type="button" onclick="Clndr.modal({ link: 'ada/intro-call' })">Book a call</button>
<script src="https://www.clndr.pro/embed.js" async></script>The guest closes it with the × button, the Escape key or a click outside. After a successful booking it closes by itself 2.5 seconds later, once the guest has seen the confirmation.
Floating button
A fixed button in a corner of every page, which opens the modal:
<script>
function mountBookingButton() {
Clndr.floatingButton({
link: 'ada/intro-call',
buttonText: 'Book a call',
position: 'bottom-right',
});
}
</script>
<script src="https://www.clndr.pro/embed.js" async onload="mountBookingButton()"></script>Put it in your site-wide footer code so it shows on every page.
Options
All three functions take link and an optional config.
elementOrSelectorstring | Elementbodyrequiredinline only. A CSS selector or a DOM element to append the iframe to. Throws if nothing matches.
linkstringbodyrequiredusername/slug of the booking page. Throws if it isn't two segments.
buttonTextstringbodydefault: Book a meetingfloatingButton only.
position'bottom-right' | 'bottom-left' | 'top-right' | 'top-left'bodydefault: bottom-rightfloatingButton only. The button sits 24px from the two edges.
config.theme'light' | 'dark' | 'auto'bodyauto follows the guest's system setting.
config.hideEventTypeDetailsbooleanbodydefault: falseHides the host's name, the page title, the duration and the description, for when your page already shows them.
config.primaryColorstringbodyAny CSS colour, used for buttons and highlights: '#9A5A16', 'rgb(74 78 143)'.
config.prefill{ name?: string; email?: string }bodyFills in the guest's name and email, for signed-in visitors.
config.accessCodestringbodyRequired for private pages: the embed has no code prompt of its own, and without the code it shows "Booking page not found".
config.onBookingSuccessful(payload) => voidbodyCalled once a booking is made. See Events.
config.onRouteChanged(payload) => voidbodyCalled when the booking page loads, with { path: '/embed/ada/intro-call' }.
Each function returns a handle. inline returns { iframe, destroy }, modal returns { destroy }, and floatingButton returns { button, destroy }. destroy() removes what was added and stops listening for its events. On a floating button it removes the button but leaves an open modal alone.
Events
Clndr.inline({
elementOrSelector: '#booking',
link: 'ada/intro-call',
config: {
onBookingSuccessful({ meetingId, startTime, endTime }) {
// meetingId is the booking's id; the times are UTC ISO strings.
window.dataLayer?.push({ event: 'booking_made', meetingId });
window.location.href = '/thanks?at=' + encodeURIComponent(startTime);
},
},
});onBookingSuccessful fires for approval pages too, when the request is sent. To act on the host's decision, use a secret key on your server: Sync bookings to your database shows how.
Under the hood these are postMessage events from the iframe. The script handles the resizing for you; Iframe embed lists the raw messages.
In Next.js or React
If you'd rather render the booking form with your own components, use @clndr-pro/react instead. To keep clndr.pro's own UI, load the script with next/script:
'use client';
import Script from 'next/script';
import { useEffect, useRef } from 'react';
type Handle = { destroy(): void };
declare global {
interface Window {
Clndr?: {
inline(options: { elementOrSelector: Element | string; link: string; config?: Record<string, unknown> }): Handle;
};
}
}
export function ClndrEmbed({ link }: { link: string }) {
const container = useRef<HTMLDivElement>(null);
const handle = useRef<Handle | null>(null);
function mount() {
if (!container.current || !window.Clndr || handle.current) return;
handle.current = window.Clndr.inline({
elementOrSelector: container.current,
link,
config: { theme: 'auto' },
});
}
useEffect(() => {
mount(); // the script may already be loaded from a previous page
return () => {
handle.current?.destroy();
handle.current = null;
};
}, [link]);
return (
<>
<Script src="https://www.clndr.pro/embed.js" strategy="afterInteractive" onReady={mount} />
<div ref={container} />
</>
);
}onReady runs once the script loads and again on every remount, and destroy() on unmount stops a client-side navigation from leaving a second frame behind.
WordPress, Webflow and other builders
- WordPress: add a Custom HTML block where the booking page should go and paste the inline snippet. For a floating button on every page, paste that snippet into your theme's footer or a header-and-footer plugin.
- Webflow: drop a Code Embed element on the page for the inline snippet. For the floating button, use Site settings → Custom code → Footer code.
- Anything else: if it accepts an HTML block with scripts, the snippets work unchanged. If it strips scripts, use the iframe.
Content Security Policy
If your site sends a CSP header, allow the script and the frame:
script-src 'self' https://www.clndr.pro;
frame-src https://www.clndr.pro;The snippets above use an inline onload attribute. Under a strict CSP that blocks inline handlers, move mountBooking into a script file of your own and attach the listener there: document.querySelector('script[src$="/embed.js"]').addEventListener('load', mountBooking).
Limits
- The embed shows clndr.pro's UI: you can change the theme and the accent colour, not the layout or the fonts.
teampages don't load inside the frame, because browsers don't send clndr.pro's sign-in cookie to a third-party iframe.