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

index.html
<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.

Clndr.modal() opens the booking page in an overlay straight away, so call it from a click:

index.html
<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:

footer code
<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 | Elementbodyrequired

inline only. A CSS selector or a DOM element to append the iframe to. Throws if nothing matches.

linkstringbodyrequired

username/slug of the booking page. Throws if it isn't two segments.

buttonTextstringbodydefault: Book a meeting

floatingButton only.

position'bottom-right' | 'bottom-left' | 'top-right' | 'top-left'bodydefault: bottom-right

floatingButton only. The button sits 24px from the two edges.

config.theme'light' | 'dark' | 'auto'body

auto follows the guest's system setting.

config.hideEventTypeDetailsbooleanbodydefault: false

Hides the host's name, the page title, the duration and the description, for when your page already shows them.

config.primaryColorstringbody

Any CSS colour, used for buttons and highlights: '#9A5A16', 'rgb(74 78 143)'.

config.prefill{ name?: string; email?: string }body

Fills in the guest's name and email, for signed-in visitors.

config.accessCodestringbody

Required 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) => voidbody

Called once a booking is made. See Events.

config.onRouteChanged(payload) => voidbody

Called 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:

components/clndr-embed.tsx
'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:

Content-Security-Policy
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.
  • team pages don't load inside the frame, because browsers don't send clndr.pro's sign-in cookie to a third-party iframe.