Calendly
Calendly is a scheduling tool that lets visitors book time on your calendar without back-and-forth emails. The Calendly embed widget renders the booking flow inline, in a popup, or behind a floating badge button.
Nuxt Scripts provides a registry script composable useScriptCalendly() and a headless <ScriptCalendlyInlineWidget> component to integrate it in your Nuxt app.
Nuxt Config Setup
Add this to your nuxt.config.ts to load Calendly globally. Alternatively you can use the useScriptCalendly composable for more control.
export default defineNuxtConfig({
scripts: {
registry: {
calendly: {
trigger: 'onNuxtReady',
}
}
}
})This config automatically enables first-party mode (bundle + proxy). See below to customise.
useScriptCalendly()
The useScriptCalendly composable lets you have fine-grain control over when and how Calendly is loaded on your site.
const { proxy } = useScriptCalendly()Please follow the Registry Scripts guide to learn more about advanced usage.
First-Party Mode: Privacy Focused Proxy
No extra config needed. The script is bundled from your domain (faster loads, no extra DNS lookup) and runtime requests are reverse-proxied through your server with automatic anonymisation (user IPs stay hidden from Calendly, works with ad blockers). Learn more.
export default defineNuxtConfig({
scripts: {
// ✅ First-party mode: bundled + proxied
registry: {
calendly: {
trigger: 'onNuxtReady',
},
},
},
})Example
Using Calendly in a component with the proxy to send events .
<script setup lang="ts">
const { proxy } = useScriptCalendly()
// noop in development, ssr
// just works in production, client
function handleAction() {
// use proxy methods here
}
</script>
<template>
<div>
<button @click="handleAction">
Send Event
</button>
</div>
</template>The composable comes with the following defaults:
- Trigger: Client Script will load when Nuxt is hydrating.
- Stylesheet: Inline The widget stylesheet (and its close-icon SVG) is inlined on first use, so no IP leak to
assets.calendly.comon render.
You can access the Calendly global as a proxy directly or await onLoaded to use it. Recommended to use the proxy for void calls; onLoaded is convenient when you need a stable DOM reference.
const { proxy } = useScriptCalendly()
function openBooking() {
proxy.Calendly.initPopupWidget({
url: 'https://calendly.com/your-name/30min',
})
}
const { onLoaded } = useScriptCalendly()
onLoaded(({ Calendly }) => {
Calendly.initInlineWidget({
url: 'https://calendly.com/your-name/30min',
parentElement: document.getElementById('calendly-inline')!,
})
})
<ScriptCalendlyInlineWidget>
The <ScriptCalendlyInlineWidget> component wraps useScriptCalendly() for the most common embed shape: an inline booking flow mounted into a host element you control.
It's optimized for performance by using Element Event Triggers, only loading the Calendly widget script once the host element comes into view. By default the trigger is 'visible'.
<script setup lang="ts">
const ready = ref(false)
</script>
<template>
<ScriptCalendlyInlineWidget
url="https://calendly.com/your-name/30min"
@ready="ready = true"
/>
</template>
Above-the-fold loading
If the widget is above the fold and you want it to start loading on hydration rather than on visibility, set above-the-fold (adds a preconnect to calendly.com) and override the trigger.
<ScriptCalendlyInlineWidget
url="https://calendly.com/your-name/30min"
above-the-fold
trigger="onNuxtReady"
/>
Prefill, UTM, and page settings
<ScriptCalendlyInlineWidget
url="https://calendly.com/your-name/30min"
:prefill="{ name: 'Ada Lovelace', email: '[email protected]' }"
:utm="{ utmSource: 'website', utmMedium: 'cta', utmCampaign: 'launch' }"
:page-settings="{ hideEventTypeDetails: true, hideGdprBanner: true }"
/>
Slots
The component exposes loading, awaitingLoad, and error slots for placeholder UX while the script trigger waits or the script load fails. The default loading slot renders an accessible spinner.
Popup and badge widgets
Popup and badge modes have no host element, so they're driven from the composable directly:
const { proxy } = useScriptCalendly()
function open() {
proxy.Calendly.initPopupWidget({
url: 'https://calendly.com/your-name/30min',
})
}
const { onLoaded } = useScriptCalendly()
onLoaded(({ Calendly }) => {
Calendly.initBadgeWidget({
url: 'https://calendly.com/your-name/30min',
text: 'Schedule time with me',
color: '#0069ff',
textColor: '#ffffff',
})
})
Prefilling invitee details and UTM parameters
All four widget initialisers (initInlineWidget, initPopupWidget, initBadgeWidget, initPopupWidgetWithText) accept prefill and utm options to pre-populate the booking form and tag the booking with marketing attribution.
<script setup lang="ts">
const { proxy } = useScriptCalendly()
function bookFromCampaign(user: { name: string, email: string }) {
proxy.Calendly.initPopupWidget({
url: 'https://calendly.com/your-name/30min',
prefill: {
name: user.name,
email: user.email,
},
utm: {
utmSource: 'website',
utmMedium: 'cta',
utmCampaign: 'launch',
},
})
}
</script>
urlstringThe Calendly event URL to embed. Required for inline, popup, and badge widgets when called via the composable.
prefillobjectPre-fill invitee fields on the booking form.
utmobjectUTM parameters appended to the booking URL for marketing attribution.
pageSettingsobjectTheme and layout overrides applied to the booking page.
parentElementstringCSS selector for the element that hosts the inline widget. Required when the widget is initialised inline; the element should have a minimum height of around 700px so the booking iframe is fully visible.
Example
Loading Calendly through app.vue when Nuxt is ready, with the inline widget rendered on a booking page.
<script setup lang="ts">
useScriptCalendly({
scriptOptions: {
trigger: 'onNuxtReady',
},
})
</script>