The vanilla JavaScript embed snippet is the lightest way to add Cal.com booking functionality to any website. It works with any tech stack and requires no build tools or dependencies.
Installation
Add the embed snippet to your HTML page. This is all you need to get started:
< script >
( function ( C , A , L ) {
let p = function ( a , ar ) { a . q . push ( ar ); };
let d = C . document ;
C . Cal = C . Cal || function () {
let cal = C . Cal ;
let ar = arguments ;
if ( ! cal . loaded ) {
cal . ns = {};
cal . q = cal . q || [];
d . head . appendChild ( d . createElement ( "script" )). src = A ;
cal . loaded = true ;
}
if ( ar [ 0 ] === L ) {
const api = function () { p ( api , arguments ); };
const namespace = ar [ 1 ];
api . q = api . q || [];
if ( typeof namespace === "string" ) {
cal . ns [ namespace ] = cal . ns [ namespace ] || api ;
p ( cal . ns [ namespace ], ar );
p ( cal , [ "initNamespace" , namespace ]);
} else p ( cal , ar );
return ;
}
p ( cal , ar );
};
})( window , "https://app.cal.com/embed/embed.js" , "init" );
</ script >
Replace https://app.cal.com with your Cal.com instance URL if you’re self-hosting.
Quick Start Examples
Inline Embed
Embed the calendar directly in your page:
<!-- 1. Add the embed snippet (shown above) -->
<!-- 2. Create a container -->
< div id = "my-cal-inline" style = "width:100%;height:100%;overflow:scroll" ></ div >
<!-- 3. Initialize the embed -->
< script >
Cal ( "init" );
Cal ( "inline" , {
elementOrSelector: "#my-cal-inline" ,
calLink: "organization/event-type"
});
</ script >
Open the calendar in a modal when a button is clicked:
<!-- 1. Add the embed snippet -->
<!-- 2. Add a button with data attributes -->
< button
data-cal-link = "organization/event-type"
data-cal-config = '{"layout":"month_view","theme":"dark"}'
>
Book a meeting
</ button >
<!-- 3. Initialize -->
< script >
Cal ( "init" );
</ script >
Add a persistent floating button to your page:
<!-- 1. Add the embed snippet -->
<!-- 2. Initialize with floating button -->
< script >
Cal ( "init" );
Cal ( "floatingButton" , {
calLink: "organization/event-type" ,
buttonText: "Book a meeting" ,
buttonPosition: "bottom-right" ,
config: {
theme: "dark"
}
});
</ script >
API Reference
Initialization
Always initialize Cal before using it:
Cal ( "init" , {
origin: "https://cal.com" , // Optional: Cal.com instance URL
debug: false , // Optional: Enable debug logging
uiDebug: false // Optional: Enable UI debug mode
});
Inline Method
Embed the calendar inline in a container:
Cal ( "inline" , {
elementOrSelector: "#my-cal-inline" , // Required: CSS selector or element
calLink: "organization/event-type" , // Required: Booking link
config: { // Optional: Configuration
name: "John Doe" ,
email: "[email protected] " ,
notes: "Initial discussion" ,
theme: "dark" ,
layout: "month_view"
}
});
Modal Method
Open the calendar in a modal programmatically:
Cal ( "modal" , {
calLink: "organization/event-type" ,
config: {
theme: "dark" ,
layout: "month_view"
}
});
Create a floating action button:
Cal ( "floatingButton" , {
calLink: "organization/event-type" ,
buttonText: "Book a meeting" , // Button text
buttonPosition: "bottom-right" , // Position: bottom-right, bottom-left, etc.
config: { // Optional: Configuration
theme: "dark"
}
});
UI Method
Update UI configuration dynamically:
Cal ( "ui" , {
theme: "dark" ,
styles: {
branding: {
brandColor: "#000000"
}
},
hideEventTypeDetails: false ,
cssVarsPerTheme: {
light: {
"cal-border-booker" : "#e5e7eb" ,
"cal-text-emphasis" : "#111827"
},
dark: {
"cal-border-booker" : "#374151" ,
"cal-text-emphasis" : "#f9fafb"
}
}
});
Configuration Options
The config object supports the following properties:
Prefill Fields
UI Configuration
config : {
theme : "dark" , // "light" | "dark" | "auto"
layout : "month_view" , // "month_view" | "week_view" | "column_view"
"ui.color-scheme" : "dark" ,
"ui.autoscroll" : "false" , // Disable auto-scroll
useSlotsViewOnSmallScreen : "true" // Use slots view on mobile
}
Iframe Attributes
config : {
iframeAttrs : {
id : "my-cal-iframe" ,
title : "Cal.com Booking"
}
}
Event Handling
Listen to booking events to integrate with your application:
Listen to Events
Cal ( "init" );
// Listen to booking success
Cal ( "on" , {
action: "bookingSuccessfulV2" ,
callback : ( e ) => {
const booking = e . detail . data ;
console . log ( "Booking created:" , {
title: booking . title ,
startTime: booking . startTime ,
endTime: booking . endTime ,
attendees: booking . attendees
});
// Track in analytics
analytics . track ( "Booking Created" , {
eventType: booking . title ,
duration: booking . duration
});
}
});
Available Events
// Listen to all events
Cal ( "on" , {
action: "*" ,
callback : ( e ) => {
console . log ( "Event:" , e . detail . type , e . detail . data );
}
});
// Listen to specific events
Cal ( "on" , {
action: "bookerReady" ,
callback : ( e ) => {
console . log ( "Booker is ready" );
}
});
Cal ( "on" , {
action: "bookingCancelled" ,
callback : ( e ) => {
console . log ( "Booking cancelled:" , e . detail . data );
}
});
Remove Event Listeners
const callback = ( e ) => {
console . log ( e . detail . data );
};
// Add listener
Cal ( "on" , {
action: "bookingSuccessfulV2" ,
callback: callback
});
// Remove listener
Cal ( "off" , {
action: "bookingSuccessfulV2" ,
callback: callback
});
Using Namespaces
Namespaces allow multiple embeds on the same page without conflicts:
< div id = "cal-1" ></ div >
< div id = "cal-2" ></ div >
< script >
// Initialize namespaces
Cal ( "init" , "namespace1" );
Cal ( "init" , "namespace2" );
// Use namespaced embeds
Cal . ns . namespace1 ( "inline" , {
elementOrSelector: "#cal-1" ,
calLink: "team/event-1"
});
Cal . ns . namespace2 ( "inline" , {
elementOrSelector: "#cal-2" ,
calLink: "team/event-2"
});
// Namespaced event listeners
Cal . ns . namespace1 ( "on" , {
action: "bookingSuccessfulV2" ,
callback : ( e ) => console . log ( "Booking from namespace1" )
});
</ script >
Prerendering
Preload the booking page for faster modal opening:
Cal ( "init" );
// Prerender when user hovers over CTA
document . querySelector ( ".book-button" ). addEventListener ( "mouseenter" , () => {
Cal ( "prerender" , {
calLink: "organization/event-type" ,
type: "modal" ,
pageType: "team.event.booking.slots"
});
});
Preloading with Router
For routing forms, prerender with routing parameters:
Cal ( "prerender" , {
calLink: "router?formId=123&department=sales" ,
type: "modal" ,
pageType: "team.event.booking.slots"
});
Advanced Examples
Dynamic Configuration
Update configuration based on user actions:
Cal ( "init" );
Cal ( "inline" , {
elementOrSelector: "#my-cal" ,
calLink: "organization/event-type"
});
// Update theme dynamically
document . querySelector ( "#dark-mode-toggle" ). addEventListener ( "click" , () => {
Cal ( "ui" , {
theme: "dark"
});
});
Prefill booking data from a form:
< form id = "contact-form" >
< input type = "text" name = "name" placeholder = "Name" >
< input type = "email" name = "email" placeholder = "Email" >
< button type = "submit" > Book Meeting </ button >
</ form >
< script >
Cal ( "init" );
document . querySelector ( "#contact-form" ). addEventListener ( "submit" , ( e ) => {
e . preventDefault ();
const formData = new FormData ( e . target );
Cal ( "modal" , {
calLink: "organization/event-type" ,
config: {
name: formData . get ( "name" ),
email: formData . get ( "email" )
}
});
});
</ script >
Auto-forward Query Parameters
Forward URL query parameters to the embed:
< script >
Cal . config = Cal . config || {};
Cal . config . forwardQueryParams = true ;
Cal ( "init" );
Cal ( "inline" , {
elementOrSelector: "#my-cal" ,
calLink: "organization/event-type"
});
</ script >
Now parameters like ?name=John&[email protected] will be forwarded to the embed.
Element Click Embed
Use data attributes for zero-JavaScript modal embeds:
<!-- Add multiple booking buttons -->
< button
data-cal-link = "organization/event-type-1"
data-cal-config = '{"theme":"light"}'
>
Book Sales Call
</ button >
< button
data-cal-link = "organization/event-type-2"
data-cal-config = '{"theme":"dark"}'
>
Book Support Call
</ button >
<!-- Initialize once -->
< script >
Cal ( "init" );
</ script >
With Namespace
< button
data-cal-namespace = "sales"
data-cal-link = "organization/sales-call"
>
Book Sales Call
</ button >
< script >
Cal ( "init" , "sales" );
</ script >
Debugging
Enable Logging
Add cal.embed.logging=1 to your page URL:
https://example.com/booking?cal.embed.logging=1
This will log:
Embed initialization
Parent-iframe communication
Event triggers
Configuration changes
Debug Mode
Enable debug mode during initialization:
Cal ( "init" , {
debug: true ,
uiDebug: true
});
The embed snippet is part of the @calcom/embed-snippet package:
{
"name" : "@calcom/embed-snippet" ,
"version" : "1.3.3" ,
"description" : "Vanilla JS embed snippet that loads @calcom/embed-core"
}
The snippet:
Loads @calcom/embed-core dynamically
Queues commands until the core library is ready
Supports multiple namespaces
Has zero dependencies
Minimal file size (~2KB)
Best Practices
Call Cal("init") once, preferably near the closing </body> tag. Multiple initializations are safe but unnecessary.
Use CSS selectors carefully
Ensure your elementOrSelector targets exist in the DOM before calling inline embed methods.
Set up event listeners with Cal("on", ...) before triggering actions that might fire those events.
For single-page applications, remove event listeners when unmounting components to prevent memory leaks.
Next Steps
Customization Options Learn about themes, styles, and branding
React Component Use the React wrapper for React apps