Cal.com vs Calendly vs Nylas: Scheduling and Calendar APIs (2026)
TL;DR
Cal.com is the open-source scheduling platform — embeddable booking, calendar sync, team scheduling, workflows, API-first, self-hostable, the open alternative to Calendly. Calendly is the scheduling automation leader — polished booking pages, routing forms, team scheduling, integrations, the most recognized scheduling brand. Nylas is the calendar and email API platform — unified calendar API across Google/Microsoft/iCloud, scheduling components, build custom calendar experiences. In 2026: Cal.com for open-source self-hosted scheduling, Calendly for plug-and-play booking, Nylas for building custom calendar integrations.
Key Takeaways
- Cal.com: @calcom/embed-react ~20K weekly downloads — open-source, self-hostable, API-first
- Calendly: calendly widget ~50K weekly downloads — polished, routing forms, team scheduling
- Nylas: nylas ~30K weekly downloads — unified calendar API, Google/Microsoft/iCloud sync
- Cal.com is fully open-source with a rich API
- Calendly has the most polished end-user scheduling experience
- Nylas provides low-level calendar API access across providers
Cal.com
Cal.com — open-source scheduling:
Embed booking widget
import Cal, { getCalApi } from "@calcom/embed-react"
import { useEffect } from "react"
function BookingPage() {
useEffect(() => {
(async () => {
const cal = await getCalApi()
cal("ui", {
theme: "dark",
styles: { branding: { brandColor: "#3B82F6" } },
hideEventTypeDetails: false,
})
})()
}, [])
return (
<Cal
calLink="royce/30min"
style={{ width: "100%", height: "100%", overflow: "scroll" }}
config={{ layout: "month_view" }}
/>
)
}
// Inline embed:
function InlineBooking() {
return (
<Cal
calLink="royce/consultation"
config={{
layout: "month_view",
theme: "dark",
}}
/>
)
}
// Popup embed:
function PopupBooking() {
useEffect(() => {
(async () => {
const cal = await getCalApi()
cal("ui", { theme: "dark" })
})()
}, [])
return (
<button
data-cal-link="royce/30min"
data-cal-config='{"layout":"month_view"}'
>
Schedule a Call
</button>
)
}
API integration
// Cal.com API v2:
const CAL_API = "https://api.cal.com/v2"
const headers = {
Authorization: `Bearer ${process.env.CAL_API_KEY}`,
"Content-Type": "application/json",
"cal-api-version": "2024-08-13",
}
// List event types:
const eventTypes = await fetch(`${CAL_API}/event-types`, { headers })
const { data: types } = await eventTypes.json()
types.forEach((type: any) => {
console.log(`${type.title} — ${type.length}min — ${type.slug}`)
})
// Get available slots:
const slots = await fetch(
`${CAL_API}/slots/available?startTime=2026-03-10T00:00:00Z&endTime=2026-03-17T00:00:00Z&eventTypeId=123`,
{ headers }
)
const { data: availability } = await slots.json()
Object.entries(availability.slots).forEach(([date, times]: [string, any]) => {
console.log(`${date}: ${times.length} slots available`)
})
// Create a booking:
const booking = await fetch(`${CAL_API}/bookings`, {
method: "POST",
headers,
body: JSON.stringify({
eventTypeId: 123,
start: "2026-03-12T10:00:00Z",
attendee: {
name: "Client Name",
email: "client@example.com",
timeZone: "America/New_York",
},
metadata: {
source: "pkgpulse",
plan: "pro",
},
}),
})
const { data: newBooking } = await booking.json()
console.log(`Booking confirmed: ${newBooking.uid}`)
Webhooks and workflows
// Cal.com webhook handler:
app.post("/api/webhooks/cal", async (req, res) => {
const { triggerEvent, payload } = req.body
switch (triggerEvent) {
case "BOOKING_CREATED":
console.log(`New booking: ${payload.title}`)
console.log(`Attendee: ${payload.attendees[0].email}`)
console.log(`Time: ${payload.startTime} — ${payload.endTime}`)
// Add to CRM, send Slack notification, etc.
await notifySlack(`📅 New booking: ${payload.title}`)
break
case "BOOKING_RESCHEDULED":
console.log(`Rescheduled: ${payload.uid}`)
break
case "BOOKING_CANCELLED":
console.log(`Cancelled: ${payload.uid}`)
break
case "MEETING_ENDED":
console.log(`Meeting ended: ${payload.uid}`)
// Trigger follow-up workflow
break
}
res.sendStatus(200)
})
// Manage event types via API:
await fetch(`${CAL_API}/event-types`, {
method: "POST",
headers,
body: JSON.stringify({
title: "Package Consultation",
slug: "consultation",
length: 30,
description: "Discuss package selection and architecture",
locations: [{ type: "integrations:google:meet" }],
bookingFields: [
{ name: "company", type: "text", label: "Company Name", required: true },
{ name: "projectSize", type: "select", label: "Project Size",
options: ["Small", "Medium", "Large"], required: true },
],
}),
})
Calendly
Calendly — scheduling automation:
Embed widget
// Inline embed:
import { InlineWidget } from "react-calendly"
function BookingPage() {
return (
<InlineWidget
url="https://calendly.com/royce/30min"
styles={{ height: "700px" }}
pageSettings={{
backgroundColor: "1a1a2e",
hideEventTypeDetails: false,
hideLandingPageDetails: false,
primaryColor: "3B82F6",
textColor: "ffffff",
}}
prefill={{
email: "client@example.com",
name: "Client Name",
customAnswers: {
a1: "PkgPulse", // Custom question answers
},
}}
utm={{
utmSource: "pkgpulse",
utmMedium: "website",
utmCampaign: "consultations",
}}
/>
)
}
// Popup widget:
import { PopupWidget } from "react-calendly"
function PopupBooking() {
return (
<PopupWidget
url="https://calendly.com/royce/30min"
rootElement={document.getElementById("root")!}
text="Schedule a Call"
textColor="#ffffff"
color="#3B82F6"
/>
)
}
// Popup button:
import { PopupButton } from "react-calendly"
function BookButton() {
return (
<PopupButton
url="https://calendly.com/royce/30min"
rootElement={document.getElementById("root")!}
text="Book a Meeting"
className="btn-primary"
/>
)
}
API integration
// Calendly API v2:
const CALENDLY_API = "https://api.calendly.com"
const headers = {
Authorization: `Bearer ${process.env.CALENDLY_ACCESS_TOKEN}`,
"Content-Type": "application/json",
}
// Get current user:
const me = await fetch(`${CALENDLY_API}/users/me`, { headers })
const { resource: user } = await me.json()
console.log(`Organization: ${user.current_organization}`)
// List event types:
const eventTypes = await fetch(
`${CALENDLY_API}/event_types?user=${user.uri}&active=true`,
{ headers }
)
const { collection: types } = await eventTypes.json()
types.forEach((type: any) => {
console.log(`${type.name} — ${type.duration}min — ${type.scheduling_url}`)
})
// List scheduled events:
const events = await fetch(
`${CALENDLY_API}/scheduled_events?user=${user.uri}&min_start_time=2026-03-01T00:00:00Z&max_start_time=2026-03-31T23:59:59Z&status=active`,
{ headers }
)
const { collection: scheduled } = await events.json()
scheduled.forEach((event: any) => {
console.log(`${event.name}: ${event.start_time} — ${event.end_time}`)
})
// Get event invitees:
const invitees = await fetch(
`${CALENDLY_API}/scheduled_events/${eventUuid}/invitees`,
{ headers }
)
const { collection: attendees } = await invitees.json()
attendees.forEach((invitee: any) => {
console.log(`${invitee.name} <${invitee.email}>`)
})
Webhooks
// Create webhook subscription:
await fetch(`${CALENDLY_API}/webhook_subscriptions`, {
method: "POST",
headers,
body: JSON.stringify({
url: "https://api.pkgpulse.com/webhooks/calendly",
events: [
"invitee.created", // New booking
"invitee.canceled", // Cancellation
"routing_form_submission.created", // Form submitted
],
organization: user.current_organization,
scope: "organization",
signing_key: process.env.CALENDLY_WEBHOOK_SECRET,
}),
})
// Webhook handler:
import crypto from "crypto"
app.post("/api/webhooks/calendly", (req, res) => {
// Verify signature:
const signature = req.headers["calendly-webhook-signature"] as string
const payload = JSON.stringify(req.body)
const expected = crypto
.createHmac("sha256", process.env.CALENDLY_WEBHOOK_SECRET!)
.update(payload)
.digest("hex")
if (signature !== expected) {
return res.sendStatus(403)
}
const { event, payload: data } = req.body
switch (event) {
case "invitee.created":
console.log(`New booking: ${data.name} <${data.email}>`)
console.log(`Event: ${data.scheduled_event.name}`)
console.log(`Time: ${data.scheduled_event.start_time}`)
// Add to CRM, send welcome email, etc.
break
case "invitee.canceled":
console.log(`Cancellation: ${data.name}`)
break
}
res.sendStatus(200)
})
Nylas
Nylas — calendar and email API:
Calendar access
import Nylas from "nylas"
const nylas = new Nylas({
apiKey: process.env.NYLAS_API_KEY!,
apiUri: "https://api.us.nylas.com",
})
// List calendars for a connected account:
const calendars = await nylas.calendars.list({
identifier: grantId, // Connected account grant ID
})
calendars.data.forEach((calendar) => {
console.log(`${calendar.name} (${calendar.id}) — ${calendar.readOnly ? "read-only" : "writable"}`)
})
// List events:
const events = await nylas.events.list({
identifier: grantId,
queryParams: {
calendarId: "primary",
start: Math.floor(new Date("2026-03-01").getTime() / 1000),
end: Math.floor(new Date("2026-03-31").getTime() / 1000),
},
})
events.data.forEach((event) => {
console.log(`${event.title}: ${event.when.startTime} — ${event.when.endTime}`)
event.participants?.forEach((p) => {
console.log(` ${p.name} <${p.email}> — ${p.status}`)
})
})
Create and manage events
import Nylas from "nylas"
const nylas = new Nylas({ apiKey: process.env.NYLAS_API_KEY! })
// Create event:
const event = await nylas.events.create({
identifier: grantId,
requestBody: {
calendarId: "primary",
title: "Package Architecture Review",
description: "Review package selection and architecture decisions",
when: {
startTime: Math.floor(new Date("2026-03-12T10:00:00-04:00").getTime() / 1000),
endTime: Math.floor(new Date("2026-03-12T10:30:00-04:00").getTime() / 1000),
},
location: "Google Meet",
participants: [
{ email: "client@example.com", name: "Client" },
],
conferencing: {
provider: "Google Meet",
autocreate: {}, // Auto-create Google Meet link
},
reminders: {
useDefault: false,
overrides: [
{ reminderMinutes: 10, reminderMethod: "popup" },
{ reminderMinutes: 60, reminderMethod: "email" },
],
},
},
queryParams: { calendarId: "primary" },
})
console.log(`Event created: ${event.data.id}`)
console.log(`Meet link: ${event.data.conferencing?.details?.url}`)
// Update event:
await nylas.events.update({
identifier: grantId,
eventId: event.data.id,
requestBody: {
title: "Package Architecture Review (Updated)",
description: "Updated agenda: review React 19 migration strategy",
},
queryParams: { calendarId: "primary" },
})
// Delete event:
await nylas.events.destroy({
identifier: grantId,
eventId: event.data.id,
queryParams: { calendarId: "primary" },
})
Scheduling and availability
import Nylas from "nylas"
const nylas = new Nylas({ apiKey: process.env.NYLAS_API_KEY! })
// Get free/busy availability:
const availability = await nylas.calendars.getFreeBusy({
identifier: grantId,
requestBody: {
emails: ["royce@example.com"],
startTime: Math.floor(new Date("2026-03-10T00:00:00Z").getTime() / 1000),
endTime: Math.floor(new Date("2026-03-14T23:59:59Z").getTime() / 1000),
},
})
availability.data.forEach((account) => {
account.timeSlots?.forEach((slot) => {
console.log(`${slot.status}: ${new Date(slot.startTime * 1000).toISOString()} — ${new Date(slot.endTime * 1000).toISOString()}`)
})
})
// Nylas Scheduler — create a booking page:
const config = await nylas.scheduler.configurations.create({
identifier: grantId,
requestBody: {
participants: [
{
email: "royce@example.com",
name: "Royce",
availability: {
calendarIds: ["primary"],
},
booking: {
calendarId: "primary",
},
},
],
availability: {
durationMinutes: 30,
intervalMinutes: 15,
availabilityRules: {
defaultOpenHours: [
{
days: [1, 2, 3, 4, 5], // Mon-Fri
timezone: "America/New_York",
start: "09:00",
end: "17:00",
},
],
roundRobinGroupId: undefined,
},
},
eventBooking: {
title: "Meeting with {{invitee_name}}",
description: "Scheduled via PkgPulse",
location: "Google Meet",
conferencing: {
provider: "Google Meet",
autocreate: {},
},
},
},
})
console.log(`Scheduler config: ${config.data.id}`)
React scheduling component
import { NylasScheduling, NylasSchedulerEditor } from "@nylas/react"
function BookingWidget() {
return (
<NylasScheduling
configurationId="config-123"
schedulerApiUrl="https://api.us.nylas.com"
nylasSessionsConfig={{
clientId: process.env.NEXT_PUBLIC_NYLAS_CLIENT_ID!,
redirectUri: "https://pkgpulse.com/callback",
accessType: "offline",
}}
eventOverrides={{
timeslotConfirmed: (event) => {
console.log("Booking confirmed:", event.detail)
// Redirect, show success, etc.
},
}}
/>
)
}
// Webhook for booking events:
app.post("/api/webhooks/nylas", async (req, res) => {
const { type, data } = req.body
switch (type) {
case "event.created":
console.log(`New event: ${data.object.title}`)
break
case "event.updated":
console.log(`Event updated: ${data.object.title}`)
break
case "event.deleted":
console.log(`Event deleted: ${data.object.id}`)
break
}
res.sendStatus(200)
})
Feature Comparison
| Feature | Cal.com | Calendly | Nylas |
|---|---|---|---|
| Open-source | ✅ | ❌ | ❌ |
| Self-hosted | ✅ | ❌ | ❌ |
| Booking pages | ✅ | ✅ | ✅ (Scheduler) |
| Embeddable widget | ✅ (React) | ✅ (React) | ✅ (React) |
| Calendar sync | Google, Microsoft, Apple | Google, Microsoft, iCloud | Google, Microsoft, iCloud |
| Team scheduling | ✅ (round-robin, collective) | ✅ (round-robin, collective) | ✅ (round-robin) |
| Routing forms | ✅ | ✅ | ❌ |
| Calendar API | ✅ (REST) | ✅ (REST) | ✅ (unified API) |
| Event management | ✅ | ✅ | ✅ (full CRUD) |
| Webhooks | ✅ | ✅ | ✅ |
| Custom booking fields | ✅ | ✅ | ✅ |
| Video conferencing | ✅ (Meet, Zoom, etc.) | ✅ (Meet, Zoom, etc.) | ✅ (auto-create) |
| Workflows/automations | ✅ | ✅ | ❌ |
| White-label | ✅ (self-host) | ❌ (paid) | ✅ (API-first) |
| Email integration | ❌ | ❌ | ✅ (full Email API) |
| Free tier | Free (open-source) | Free (1 event type) | Free (5 grants) |
| Pricing | Free / from $12/mo | From $10/seat/mo | From $49/mo |
When to Use Each
Use Cal.com if:
- Want an open-source scheduling platform you can self-host
- Need full customization and white-label booking pages
- Building scheduling into your product with API-first approach
- Want to avoid vendor lock-in for scheduling infrastructure
Use Calendly if:
- Want the most polished, ready-to-use scheduling experience
- Need routing forms and advanced team scheduling workflows
- Building sales/customer-facing booking with brand recognition
- Prefer plug-and-play over API customization
Use Nylas if:
- Need unified calendar API access across Google/Microsoft/iCloud
- Building custom calendar features into your application
- Need both email and calendar integration in one API
- Want low-level calendar CRUD beyond just scheduling/booking
Methodology
Download data from npm registry (weekly average, February 2026). Feature comparison based on @calcom/embed-react v1.x, react-calendly v4.x, and nylas v7.x.