packages/embeds/LIFECYCLE.md
This document details the lifecycle events and states of Cal.com embeds, showing the interaction flow between the parent page and the iframe.
The handshake is the foundational communication protocol that establishes a bi-directional channel between the parent page and the iframe. All other embed functionality depends on this handshake being completed successfully.
See embed-handshake.mermaid for the detailed handshake sequence. See embed-message-protocol.mermaid for the message passing architecture.
__iframeReady via postMessage when ready to receive commandsparentKnowsIframeReady back to iframelinkReady (or linkPrerendered)All messages use the originator: "CAL" identifier to distinguish Cal.com embed messages:
// Parent → Iframe (Commands)
{ originator: "CAL", method: "ui", arg: { theme: "dark" } }
// Iframe → Parent (Events)
{ originator: "CAL", type: "__iframeReady", data: { isPrerendering: false } }
Inline embeds are created immediately when Cal.inline() is called. They have a simpler lifecycle without prerendering support.
See inline-embed-lifecycle.mermaid for the complete sequence diagram.
Modal embeds are created when a CTA is clicked or Cal.modal() is called. They support reuse, state management, and prerendering.
See modal-embed-lifecycle.mermaid for the complete sequence diagram.
Prerendering allows loading the booking page in the background before the user opens the modal, enabling instant display when the CTA is clicked.
See modal-prerendering-flow.mermaid for the complete sequence diagram.
The embed system carefully manages visibility to prevent visual glitches:
Initial Load
iframe Creation
__iframeReady Event
__dimensionChanged Event
__windowLoadComplete Event
linkReady Event
parentKnowsIframeReady Event
__connectInitiated Event
__connectCompleted Event
linkPrerendered Event
bookerViewed Event
bookerReopened Event
bookerReloaded Event
bookerReady Event
Prerendering loads the booking page in the background before the user needs it:
Prerender Phase:
prerender=true parameterConnect Phase (when user opens the modal):
connect() with user's configurationThe embed system queues commands sent before the iframe is ready. Once the iframe is ready, all queued commands are processed in order, and new commands execute immediately.
┌──────────────────────────────────────────────────────────────┐
│ doInIframe(cmd) │
└─────────────────────────┬────────────────────────────────────┘
│
▼
┌─────────────────────┐
│ iframeReady? │
└─────────┬───────────┘
│
┌──────────────┼──────────────┐
│ No │ │ Yes
▼ │ ▼
┌───────────────┐ │ ┌───────────────┐
│ Push to queue │ │ │ postMessage │
│ (iframeDoQueue)│ │ │ to iframe │
└───────────────┘ │ └───────────────┘
│
│ On __iframeReady event:
▼
┌─────────────────────┐
│ Flush queue: │
│ forEach → postMessage│
│ Clear queue │
└─────────────────────┘
| Method | Purpose | Example |
|---|---|---|
ui | Apply styles, theme, branding | { method: "ui", arg: { theme: "dark" } } |
parentKnowsIframeReady | Acknowledge handshake | { method: "parentKnowsIframeReady" } |
connect | Activate prerendered embed | { method: "connect", arg: { config, params } } |
Think of the embed like window.open("url", "cal-booker") with a hypothetical enhancement - when you close the popup, it stays in the background ready to spring back:
bookerViewed = Opening a new popup window (first time, or after previous was destroyed due to staleness)bookerReopened = Clicking CTA that targets the same window name, bringing back the hidden popupbookerReloaded = Popup window navigating to a new URL (full page reload within the same popup)This mental model helps understand the lifecycle:
bookerViewed event (similar to opening a new popup)bookerReopened event (similar to focusing a hidden popup)bookerViewed event (similar to opening a new popup after the old one was closed)bookerReloaded event (similar to popup navigating to new URL)The embed system tracks user interactions and page views:
Page Load Errors: