Booking Embed
The booking embed lets you display the SavvyCal appointment booking experience directly on your website. It's a small, SavvyCal-hosted web component that wraps an <iframe> pointing at a booking URL. The booking interface runs inside the iframe; the component handles sizing, theming, and lifecycle events.
There are two ways to embed:
- Inline — the booking interface renders directly in the page.
- Popup — a trigger opens the booking interface in a full-screen overlay.
This is version 2 of the booking embed. The previous <savvycal-booking-embed> component is still supported and documented in Booking Embed (v1), but we recommend new integrations use the component described here.
Live Demo
Inline — the booking interface renders directly in the page:
Popup — a trigger opens the booking interface in a full-screen overlay:
Installation
Before embedding, add your site's domain to your Allowed Origins list on the Settings → Allowed origins page (just the hostname, e.g. myapp.com or subdomain.myapp.com). If you're using localhost, include the port if it differs from 80 (e.g. localhost:3000).
Include the embed script once per page:
<script async src="https://js.savvycal.app/v2/embed.js"></script>
This registers the <savvycal-booking> and <savvycal-booking-button> elements and the SavvyCal JavaScript API.
Booking URLs
Both embed types load a booking URL that you supply via the src attribute. Booking URLs come from booking links. You can create a booking link when editing a service, under the Booking Links tab, or via the Booking Links API.
Customize the booking experience by adding query parameters to the booking URL:
| Parameter | Values | Description |
|---|---|---|
theme | light or dark | Color theme of the booking interface. Defaults to light. |
show_header | false | Hide the service name and duration shown above the form. Shown by default. |
locale | auto, en, or es | Interface language. Defaults to auto, which matches the visitor's browser. |
For example, a dark-themed embed with the header hidden:
YOUR_BOOKING_URL?theme=dark&show_header=false
The component automatically appends a few parameters of its own (the embed mode, a per-instance identifier, and the host origin) when it loads the iframe. You don't need to set these yourself.
For privacy, the booking URL does not accept prefilled client details (name, email, phone, etc.) via query parameters. Client details are collected within the booking flow.
Inline embed
Add a <savvycal-booking> element where you want the booking interface to appear:
<script async src="https://js.savvycal.app/v2/embed.js"></script>
<savvycal-booking
src="YOUR_BOOKING_URL?theme=light"
></savvycal-booking>
The inline embed renders with a transparent background and no border or padding, fills the width of its container, and grows to fit its content. Wrap it in your own container to control its width and placement.
Popup embed
Use a <savvycal-booking-button> element as a trigger. Clicking it (or pressing Enter or Space) opens the booking interface in a full-screen overlay:
<script async src="https://js.savvycal.app/v2/embed.js"></script>
<savvycal-booking-button
src="YOUR_BOOKING_URL?theme=light"
>
Book an appointment
</savvycal-booking-button>
The element itself is the clickable trigger — style it to match your site with your own CSS. Don't place a <button> inside it; the element already behaves as a button.
To open the popup from your own code (for example, from an existing button), call:
SavvyCal.openPopup("YOUR_BOOKING_URL?theme=light");
The popup closes when the visitor clicks outside the booking card or presses Escape. You can customize the dimmed backdrop with the --savvycal-backdrop-color CSS variable:
:root {
--savvycal-backdrop-color: rgba(0, 0, 0, 0.6);
}
Events
The embed reports lifecycle events to the host page as DOM events. Listen for them on window:
| Event | Description |
|---|---|
savvycal:appointment.created | Fired when a visitor completes a booking. |
window.addEventListener("savvycal:appointment.created", (e) => {
const { appointmentId } = e.detail;
// Example: redirect to a thank-you page
// window.location.href = "/thank-you?appointment=" + appointmentId;
});
savvycal:appointment.created carries only the new appointment's ID. It shares its name with the appointment.created webhook event but not its payload — fetch full appointment details from your server via the API if you need them.
All embed events are namespaced with a savvycal: prefix and dispatched as DOM events of the same name, so you can listen for new events as they're introduced.