Skip to main content

Documentation Index

Fetch the complete documentation index at: https://calcomhelp-mintlify-onboarding-embed-1775554265.mintlify.app/llms.txt

Use this file to discover all available pages before exploring further.

The onboarding embed allows you to add a “Continue with Cal.com” button to your application. When a user clicks it, a dialog opens where they can sign up for Cal.com, set up their profile, connect a calendar, and authorize your app with OAuth — all in one flow, without ever leaving your site. This is useful if you are building an integration that needs access to a user’s Cal.com account. Instead of sending users to Cal.com separately, you can embed the entire signup and authorization experience directly in your app.
The onboarding embed is a React component available in the @calcom/atoms package. You need an OAuth client ID from Cal.com to use it. See OAuth setup for details on obtaining credentials.

How it works

The onboarding embed guides users through four steps inside a modal dialog:
  1. Sign up or log in — The user creates a new Cal.com account or signs in to an existing one.
  2. Profile setup — The user enters their name, username, and bio.
  3. Calendar connection — The user optionally connects a calendar (Google Calendar, Apple Calendar, etc.).
  4. OAuth authorization — The user reviews the permissions your app is requesting and clicks “Allow” or “Deny”.
If a user closes the dialog before finishing, they can reopen it and resume where they left off.

Installation

Install the @calcom/atoms package:
npm install @calcom/atoms

Basic usage

Import the OnboardingEmbed component and add it to your page:
import { OnboardingEmbed } from "@calcom/atoms";

function App() {
  const state = crypto.randomUUID();

  return (
    <OnboardingEmbed
      oAuthClientId="your-client-id"
      authorization={{
        redirectUri: "https://yourapp.com/callback",
        scope: ["EVENT_TYPE_READ", "BOOKING_READ"],
        state: state,
      }}
      onAuthorizationAllowed={({ code }) => {
        // Exchange this code for an access token on your server
        console.log("Authorization code:", code);
      }}
      onAuthorizationDenied={() => {
        console.log("User denied access");
      }}
      onError={(error) => {
        console.error("Onboarding error:", error);
      }}
    />
  );
}
By default, the component renders a “Continue with Cal.com” button. Clicking it opens the onboarding dialog.

Configuration

Required props

PropTypeDescription
oAuthClientIdstringYour OAuth client ID registered with Cal.com.
authorizationobjectOAuth authorization parameters (see below).

Authorization object

PropertyTypeRequiredDescription
redirectUristringYesOne of the redirect URIs registered on your OAuth client. Must share the same origin as the page hosting the embed.
scopestring[]YesOAuth scopes to request, such as EVENT_TYPE_READ, BOOKING_WRITE, or SCHEDULE_READ.
statestringYesA random string to prevent CSRF attacks. Use crypto.randomUUID() to generate one.
codeChallengestringNoPKCE code challenge for public clients that cannot store a client secret. Uses the S256 method.

Optional props

PropTypeDefaultDescription
hoststringhttps://app.cal.comThe Cal.com host URL. Change this if you are using a self-hosted instance.
theme"light" or "dark""light"Controls the appearance of the button and dialog.
userobjectPre-fill the signup form with email, name, and username.
triggerReactNode”Continue with Cal.com” buttonA custom element to open the onboarding dialog.
onAuthorizationAllowedfunctionCalled when the user clicks “Allow”. Receives { code } with the authorization code.
onAuthorizationDeniedfunctionCalled when the user clicks “Deny”.
onErrorfunctionCalled when an error occurs. Receives an error object with code and message.
onClosefunctionCalled when the dialog is dismissed.

Authorization modes

The onboarding embed supports two ways to handle the authorization result.

Callback mode

If you provide the onAuthorizationAllowed prop, the component calls your function with the authorization code and closes the dialog. This keeps the user on your page.
<OnboardingEmbed
  oAuthClientId="your-client-id"
  authorization={{
    redirectUri: "https://yourapp.com/callback",
    scope: ["EVENT_TYPE_READ"],
    state: crypto.randomUUID(),
  }}
  onAuthorizationAllowed={({ code }) => {
    // Send the code to your server to exchange for tokens
    fetch("/api/exchange-token", {
      method: "POST",
      body: JSON.stringify({ code }),
    });
  }}
  onAuthorizationDenied={() => {
    // Handle denial
  }}
/>

Redirect mode

If you do not provide onAuthorizationAllowed, the browser redirects to your redirectUri with the authorization code and state as URL parameters:
https://yourapp.com/callback?code=AUTHORIZATION_CODE&state=YOUR_STATE
If the user denies access, the redirect includes an error parameter:
https://yourapp.com/callback?error=access_denied&state=YOUR_STATE

Pre-filling user details

You can pass a user prop to pre-fill the signup form:
<OnboardingEmbed
  oAuthClientId="your-client-id"
  user={{
    email: "jane@company.com",
    name: "Jane Smith",
    username: "janesmith",
  }}
  authorization={{
    redirectUri: "https://yourapp.com/callback",
    scope: ["EVENT_TYPE_READ"],
    state: crypto.randomUUID(),
  }}
  onAuthorizationAllowed={({ code }) => {
    console.log("Authorization code:", code);
  }}
/>

Using PKCE

For public clients that cannot securely store a client secret, use PKCE (Proof Key for Code Exchange) by including a codeChallenge in the authorization object:
<OnboardingEmbed
  oAuthClientId="your-client-id"
  authorization={{
    redirectUri: "https://yourapp.com/callback",
    scope: ["EVENT_TYPE_READ"],
    state: crypto.randomUUID(),
    codeChallenge: "your-generated-code-challenge",
  }}
  onAuthorizationAllowed={({ code }) => {
    // Exchange the code along with your code_verifier
    console.log("Authorization code:", code);
  }}
/>

Custom trigger element

By default, the embed renders a “Continue with Cal.com” button. You can replace it with your own element:
<OnboardingEmbed
  oAuthClientId="your-client-id"
  authorization={{
    redirectUri: "https://yourapp.com/callback",
    scope: ["EVENT_TYPE_READ"],
    state: crypto.randomUUID(),
  }}
  trigger={<button className="my-button">Connect your calendar</button>}
  onAuthorizationAllowed={({ code }) => {
    console.log("Authorization code:", code);
  }}
/>

Error handling

The onError callback receives an error object with a code and message. The possible error codes are:
Error codeDescription
INVALID_PROPSRequired props are missing (such as oAuthClientId or authorization).
SIGNUP_FAILEDAccount creation failed.
ONBOARDING_FAILEDA step in the onboarding process failed.
AUTHORIZATION_FAILEDThe OAuth authorization step failed.
STATE_MISMATCHThe state parameter in the response does not match the one you provided. This may indicate a CSRF attack.
UNKNOWNAn unexpected error occurred.
<OnboardingEmbed
  oAuthClientId="your-client-id"
  authorization={{
    redirectUri: "https://yourapp.com/callback",
    scope: ["EVENT_TYPE_READ"],
    state: crypto.randomUUID(),
  }}
  onError={(error) => {
    switch (error.code) {
      case "INVALID_PROPS":
        console.error("Check your component configuration");
        break;
      case "STATE_MISMATCH":
        console.error("Possible security issue — state does not match");
        break;
      default:
        console.error("Onboarding error:", error.message);
    }
  }}
  onAuthorizationAllowed={({ code }) => {
    console.log("Authorization code:", code);
  }}
/>