# Embed script

Add a clndr.pro booking page to any website with one script tag, inline, in a modal or behind a floating button, and get a callback when a guest books.

`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

```html title="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.

## Modal

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

```html title="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:

```html title="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`.

**elementOrSelector**

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

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

**buttonText**

`floatingButton` only.

**'bottom-right' | 'bottom-left' | 'top-right' | 'top-left'**

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

**'light' | 'dark' | 'auto'**

`auto` follows the guest's system setting.

**config.hideEventTypeDetails**

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

**config.primaryColor**

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

**{ name?: string; email?: string }**

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

**config.accessCode**

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**

Called once a booking is made. See [Events](#events).

**config.onRouteChanged**

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

```js
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](/tutorials/sync-bookings) shows how.

Under the hood these are `postMessage` events from the iframe. The script handles the resizing for you; [Iframe embed](/embed/iframe#events) 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`](/react/components) instead. To keep clndr.pro's own UI, load the script with `next/script`:

```tsx title="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](/embed/iframe).

## Content Security Policy

If your site sends a CSP header, allow the script and the frame:

```text title="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.
