Cal.com and the Cal.diy fork, booking on your own box
Cal.com is an open scheduling platform: booking pages, event types and calendar sync. In spring 2026 the calcom/cal.com repository was renamed to calcom/cal.diy, relicensed to MIT and stripped of all commercial code. Without knowing that date, half the guides on the internet point at the wrong place.
What Cal.com actually does
Three different things live under one name today, and confusing them causes most of the misunderstandings.
The first is the hosted service at cal.com. You create an account, get a public page, define event types with duration, buffers before and after, minimum notice and availability rules, and the person booking picks a free slot. Cal.com writes the event into your calendar, attaches a video conferencing link and sends reminders.
The second is the source code. Until spring 2026 this was the calcom/cal.com monorepo split into an open part and a commercial directory. That address now answers with a 301 redirect to calcom/cal.diy, and the project under the new name describes itself as a fork of Cal.com with the commercial code removed.
The third is Cal.com Platform, meaning React components and the v2 API for teams that want to put booking inside their own product instead of sending users off to a Cal.com page. It is a separate paid product, even though the component library sits in the public npm registry.
The technical layer is spelled out in the README: Next.js, tRPC, React, Tailwind CSS, Prisma and Daily.co. The size of the domain shows in the database schema: packages/prisma/schema.prisma on the main branch contains 100 models, from EventType and Booking through CalendarCache and OutOfOfficeEntry to PlatformOAuthClient.
The licence from three sources and the move to Cal.diy
I checked the licence three ways, because each one shows something different.
The first source is the file in the repository. On main the LICENSE file is 21 lines long and is a plain MIT licence headed Copyright (c) 2020-present Cal.com, Inc.. I also checked the name and location variants that projects with a licence split keep the closed part in: LICENSE.md, LICENSE.txt, LICENSE-EE, licenses/LICENSE-EE, licenses/LICENSE-AGPL, licenses/LICENSE-MIT, COPYING, NOTICE, ee/LICENSE, packages/features/ee/LICENSE, packages/platform/LICENSE and apps/api/LICENSE. All return 404. There is no split.
There used to be one. Under tag v6.2.0 and the older v5.0.0 the same LICENSE file opens with the sentence Portions of this software are licensed as follows and divides the repository three ways: the packages/features/ee and apps/api/v2/src/ee directories under the commercial licence defined in ee/LICENSE, third-party components under their authors' licences, and everything else under AGPLv3. The packages/features/ee/LICENSE file at tag v5.0.0 is titled The Cal.com Commercial License. The last change to LICENSE in the history of main is the commit refactor: Cal.diy (#28903) dated 15 April 2026.
The old commercial licence is worth reading, because it still binds anyone working on code from before that date. You may use it in production only if you hold a valid Cal.com Enterprise Edition subscription for the correct number of hosts as defined in the commercial terms. You may modify it and publish patches, but the rights to those modifications stay with Cal.com and the patches themselves also require a subscription. You may copy and modify without a subscription for development and testing only. Copying, merging, publishing, distributing, sublicensing and selling are forbidden. There is no numeric threshold anywhere: the host count is set in the contract, not in the licence text, and there is no conversion date into an open licence of the kind BSL uses.
When do the AGPL obligations trigger, given that this is a tool you embed into someone else's product? Section 13 of AGPLv3 talks about modifying the program and making it available to users over a network. If you run an unmodified instance from before April 2026, you have nothing to publish, because the sources corresponding to what you run are already public. If you add your own changes and your customers click through that interface in a browser, you must offer them the sources of your version. Embedding booking through an iframe does not infect the host page, since that is a separate work talking across a frame boundary, but the embed script served from your instance is part of the program and follows the same rules. Code after 15 April 2026 removes the question entirely, because MIT imposes no obligation to release sources. The relicence does not work retroactively, though: copies distributed earlier stay under AGPLv3 plus the commercial licence, and so does any fork made before that date.
One unpleasant point deserves its own paragraph: the commercial Cal.com no longer has a public repository. The new project's README points you at a sales form for on-prem access. The code that actually powers the hosted service stopped being readable from the outside.
The npm packages, a separate licensing story
The second source is the license field in the registry. The three embed packages declare SEE LICENSE IN LICENSE, meaning they defer to a file in the tarball. The @calcom/atoms package at the version tagged latest, that is 2.6.7 from 19 August 2026, has no license field at all. The deprecated versions 2.10.1 and 2.11.0 from 3 May 2026 declared AGPL-3.0-or-later.
The third source is the contents of the published tarball. I unpacked @calcom/embed-react 1.5.3. The archive holds 21 entries, including package/LICENSE. The heading reads The Cal.com Commercial License (EE) license, and the body is the same commercial licence as in the old repository. The decisive part is the second-to-last sentence: any portion served client-side as an image, font, stylesheet, or a file compiled or combined into client-side JavaScript is copyrighted under AGPLv3. The entire contents of this package are dist/Cal.es.js, dist/Cal.es.mjs, dist/Cal.umd.js and type declarations, which is precisely client-side code. Read literally, the AGPL carve-out covers everything the package contains and the commercial licence is left with no subject matter. That is my reading of the text, not a Cal.com position, and for a serious deployment it should be confirmed with the vendor.
The @calcom/atoms 2.6.7 tarball is the other extreme: 3136 files and 13.77 MB unpacked, not one licence file, and an empty license field in package.json. Compiled code with no statement of terms at all.
The registry also carries a version higher than latest that is marked deprecated. The tags look like this: latest points at 2.6.7, maintenance points at 2.6.5 from 5 August 2026, beta points at 1.9.0-types2. Versions 2.7.0 through 2.11.0 carry higher numbers than latest and all bear the message Package no longer supported. Anyone who once pinned 2.11.0 is sitting on a deprecated branch, and the package manager will not offer a move back to 2.6.7, because that number is lower.
Peer dependency ranges inside the same family drift too. @calcom/embed-react 1.5.3 requires react in the range ^18.2.0 || ^19.0.0, while @calcom/atoms 2.6.7 asks for ^18.0.0 || ^19.0.0. On React 18.0 or 18.1 the first package reports a conflict and the second does not. The regular dependencies of @calcom/embed-react are only @calcom/embed-core 1.5.3 and @calcom/embed-snippet 1.3.3, pinned exactly, with no ranges. @calcom/atoms has a completely different tree and pins it just as tightly, among others @tanstack/react-query at 5.17.19, tailwindcss at 4.1.17, marked at 15.0.6, dompurify at 3.4.0, plus aliases such as @radix-ui/react-dialog-atoms pointing at @radix-ui/react-dialog@1.0.4. If your application uses a newer react-query, two copies end up in the tree.
There is one concrete defect in the @calcom/atoms tarball as well. The exports field describes a dozen subpaths pointing at source files, while the files field publishes only three directories.
{
"files": ["dist", "globals.min.css", "fonts"],
"exports": {
".": {
"import": "./dist/cal-atoms.js",
"types": "./dist/index.d.ts",
"require": "./dist/cal-atoms.umd.cjs"
},
"./availability/AvailabilitySettings": "./availability/AvailabilitySettings.tsx",
"./hooks/useAtomsContext": "./hooks/useAtomsContext.ts",
"./timezone": "./timezone/index.tsx",
"./globals.tw3.min.css": "./globals.tw3.min.css"
}
}The 2.6.7 archive contains no availability directory, no hooks, no timezone and no globals.tw3.min.css file. The only stylesheet in the package is globals.min.css. An import through any of those subpaths fails module resolution, so stay on the main entry point.
Self-hosting by the numbers
The reputation for a hard self-host comes mainly from the size of .env.example, and that is a misleading signal. The file on main has 483 lines, 174 uncommented assignments and 182 unique keys if you count the commented ones too. But five are required.
The runtime variable table in the README marks exactly three as required: DATABASE_URL, NEXTAUTH_SECRET and CALENDSO_ENCRYPTION_KEY. Marked optional with stated defaults are NEXT_PUBLIC_WEBAPP_URL, defaulting to http://localhost:3000, and NEXTAUTH_URL, which defaults to {NEXT_PUBLIC_WEBAPP_URL}/api/auth. Building your own image adds MAX_OLD_SPACE_SIZE with a default of 4096. The README adds the note Must match build variable to two of those keys, so a secret set differently in the build and in the container will break sign-in.
git clone https://github.com/calcom/cal.diy.git
cd cal.diy
cp .env.example .env
# the NextAuth cookie key, 32 random bytes
openssl rand -base64 32
# the application data encryption key, 24 bytes give 32 base64 characters
openssl rand -base64 24
# the full development environment: Postgres in Docker, migrations, seed data
yarn dxThat last command needs docker and docker compose installed. It brings up a local database and seeds test accounts whose credentials it prints to the console. Among them are free@example.com with the password free, pro@example.com with the password pro and admin@example.com with the password ADMINadmin2022!. The full list is visible through yarn db-studio at http://localhost:5555.
The prerequisites in the README are Node 18 or newer, PostgreSQL 13 or newer, and Yarn as the recommended package manager. The database is the only hard external dependency. An SMTP server is needed if confirmation emails are to go out, and it is configured through EMAIL_FROM, EMAIL_SERVER_HOST, EMAIL_SERVER_PORT, EMAIL_SERVER_USER and EMAIL_SERVER_PASSWORD. The example file also carries a commented-out RESEND_API_KEY, so if you prefer a transactional sender over your own SMTP, Resend is supported without writing code. Rate limiting through Unkey is optional and the README states plainly that the app works normally without it.
Memory deserves its own paragraph. For development the README recommends NODE_OPTIONS="--max-old-space-size=16384", that is 16 GB for the Node process. The image build gets 4096 MB by default. This is not an application you put on the smallest instance your provider sells. The container image sits on Docker Hub as calcom/cal.diy, and ARM variants carry a tag suffix. The example given in the README is calcom/cal.diy:v5.6.19-arm, a tag older than the current releases, which shows how far the documentation trails the code. If you want a layer that sets up the reverse proxy and certificates for you, Dokploy does exactly that job.
Calendar integrations require your own OAuth applications and there is no way around it. The Google instructions in the README run to eleven steps: your own Google Cloud project, enabling the Google Calendar API, the consent screen, adding the .../auth/calendar.events and .../auth/calendar.readonly scopes, listing test accounts, creating a Web Application OAuth client ID, two redirect URIs of the form <url>/api/integrations/googlecalendar/callback and <url>/api/auth/callback/google, downloading the JSON file and pasting its entire contents as the value of GOOGLE_API_CREDENTIALS. After that you still run yarn seed-app-store in packages/prisma and publish the consent screen, because while the app stays in testing mode it works only for accounts listed as Test Users.
Microsoft is six steps in Azure App Registration, with a multitenant registration, the redirect URI <url>/api/integrations/office365calendar/callback and the variables MS_GRAPH_CLIENT_ID and MS_GRAPH_CLIENT_SECRET. Zoom is twelve steps with the meeting:write:meeting and user:read:settings scopes. Daily.co is four steps and the DAILY_API_KEY key.
The most important self-hosting fact, though, is about what the open repository no longer contains. The README lists as removed: Teams, Organizations, Insights, Workflows and SSO with SAML. I checked this against the database schema on main and the picture is mixed. The Team, Membership, OrganizationSettings, TeamBilling and OrganizationBilling models are still in the schema, but there is no Workflow model and no routing forms model, and the packages/app-store/routing-forms directory does not answer on any of the paths I probed. Routing forms, the very feature many teams pick Cal.com for, does not exist in the open code. There is also no hosted version of Cal.diy: the README states plainly that you run the project on your own infrastructure only.
Embedding booking into your own product
There are two routes and they differ in everything: price, licence and how much control you get over the look.
The first is the embed, an iframe pointing at a booking page. The Cal component from @calcom/embed-react takes calLink as its only required prop, plus calOrigin, namespace, config, initConfig with the debug and uiDebug fields, embedJsUrl and all the attributes of a regular div.
import Cal, { getCalApi } from "@calcom/embed-react"
import { useEffect } from "react"
export function BookingPanel() {
useEffect(() => {
;(async () => {
const cal = await getCalApi({ namespace: "onboarding" })
cal("ui", {
theme: "light",
layout: "month_view",
hideEventTypeDetails: false,
cssVarsPerTheme: {
light: { "cal-brand": "#1f2937" },
dark: { "cal-brand": "#e5e7eb" }
}
})
})()
}, [])
return (
<Cal
namespace="onboarding"
calLink="team/intro-call"
calOrigin="https://cal.example.com"
config={{ layout: "month_view", theme: "light" }}
style={{ width: "100%", height: "100%", overflow: "scroll" }}
/>
)
}The field names in the cal("ui", ...) call come from the UiConfig type in @calcom/embed-core and are exactly these: hideEventTypeDetails, theme, styles, cssVarsPerTheme, layout and colorScheme. The permitted layouts are month_view, week_view and column_view. The EmbedStyles type lets you override the background of eventTypeListItem, enabledDateButton, disabledDateButton and availabilityDatePicker, and that is where styling ends. This is a frame, not a component inside your React tree.
The second route is Cal.com Platform and the @calcom/atoms package. Here you get real components. Exported names include Booker, BookerEmbed, AvailabilitySettings, CalendarSettings, CalendarView, CreateEventType, EventTypeSettings, ListEventTypes, GcalConnect, OutlookConnect, StripeConnect, PaymentForm, Router, TroubleShooter and OnboardingEmbed, alongside the hooks useBookings, useBooking, useCancelBooking, useEventTypes, useAvailableSlots, useConnectedCalendars, useMe, useTeams and useAtomsContext.
import { CalProvider, Booker, useBookings } from "@calcom/atoms"
import "@calcom/atoms/globals.min.css"
export function Schedule({ accessToken }: { accessToken: string }) {
return (
<CalProvider
clientId={process.env.NEXT_PUBLIC_CAL_OAUTH_CLIENT_ID as string}
accessToken={accessToken}
options={{
apiUrl: "https://api.cal.com/v2",
refreshUrl: "/api/cal/refresh"
}}
autoUpdateTimezone={true}
onTokenRefreshError={(error) => console.error(error)}
>
<Booker eventSlug="intro-call" username="team" />
</CalProvider>
)
}The CalProvider prop names come from the type declarations in the 2.6.7 package: clientId, accessToken, options with the apiUrl and refreshUrl fields, autoUpdateTimezone, labels, language, onTimezoneChange, onTokenRefreshStart, onTokenRefreshSuccess, onTokenRefreshError, version, organizationId and isEmbed. The in-code documentation notes that refreshUrl is required if you pass accessToken, because managed user tokens expire and the library will call that endpoint itself for a new one.
And this is where the rest of the stack comes back. Atoms give you the booking interface, but managed users have to get their identity from somewhere, so the sign-in layer stays on your side, whether on Clerk or on Better Auth. Confirmations and reminders in your own branding are transactional email, so again Resend or an equivalent sender. And if a booking should raise a notification in the app, in Slack and by email according to the recipient's preferences, a separate layer such as Knock earns its place, because Cal.com webhooks stop at emitting the event.
Cloud pricing and what you actually pay for
The price list at cal.com/pricing renders in raw HTML, so it could be read without a browser. The free plan is labelled Free forever and covers one user, unlimited event types and calendars, email and SMS notifications, integrations with over a hundred apps, the mobile app, the browser extension, Stripe and PayPal payments, two-way Salesforce and HubSpot sync and importing events from Calendly.
The Teams plan costs 12 dollars per user per month, the Organizations plan 28 dollars per user per month, both with a fourteen-day trial. Enterprise is priced Custom with a note about annual billing.
There is a discrepancy here that has to be named. The toggle on the page sits at YEARLY and Save 25% appears next to it, but the raw HTML contains only the figures 12 and 28. The monthly price is not rendered without JavaScript, so I did not confirm it. If the 25 percent discount is taken off the monthly price, the result is roughly 16 and roughly 37.33 dollars, but that is my arithmetic, not a number from the page. Likewise the address cal.com/platform/pricing returns a document of identical length to cal.com/pricing, namely 1,587,789 bytes, so no separate Platform price list is visible without JavaScript.
So what is paid for despite the open code? In the Teams plan: shared team availability, round robin, managed and collective event types, recurring events, customisable notification copy, removing Cal.com branding, routing forms, booking analytics and custom APIs. In the Organizations plan: unlimited sub-teams, routing by custom variables, a company subdomain, SAML SSO and SCIM, SOC 2, HIPAA and ISO 27001 compliance, instant meetings, domain-wide delegation and role-based permissions. Putting that next to the previous section completes the picture: routing forms are paid in the cloud and simultaneously removed from the open repository, so there is no path on which you get them for free on your own box.
Cal.com against the alternatives
| Feature | Cal.com cloud | Cal.diy self-hosted | Calendly |
|---|---|---|---|
| Code licence | no public repository | MIT | closed |
| Free entry point | 1 user, Free plan | full code, server cost | 1 event type, 1 calendar |
| Price per seat, annual billing | 12 USD Teams, 28 USD Organizations | no licence fees | 10 USD Standard, 16 USD Teams |
| Entry threshold for the company plan | no stated seat minimum | not applicable | from 15,000 USD a year, from 50 seats |
| Round robin and teams | Teams plan | removed from the repository | Teams plan |
| Routing forms | Teams plan | absent from the code | Teams plan |
| SAML SSO | Organizations plan | removed from the repository | add-on on Teams, full on Enterprise |
| React components | Platform, the @calcom/atoms package | not applicable | none |
The arithmetic on the Calendly threshold closes neatly: 15,000 dollars a year across 50 seats is 300 dollars per seat per year, that is 25 dollars a month, slightly under the 28 dollars of the Cal.com Organizations plan. The difference is that Cal.com states no seat minimum, while Calendly sets a floor of fifty and notes that billing is in dollars only.
The choice comes down to three questions. If you want a finished tool for a team and have no plan to touch the code, the difference between Cal.com and Calendly is the price of round robin, since Cal.com puts it in the 12 dollar plan and Calendly in the 16 dollar one. If you want to put booking inside your own product, only one option counts, because Calendly publishes no React components. And if self-hosting is the reason for the choice, read the list of features removed from Cal.diy before you count the savings.
Common mistakes
The first is looking for the calcom/cal.com repository and assuming nothing changed. The address still works, because GitHub redirects, but it leads to a different project with a different licence and a different feature set. Every guide written before April 2026 describes code that is no longer at that address.
The second is assuming MIT applies retroactively. A fork you made in 2025 or an instance built from tag v6.2.0 stay under AGPLv3, and in those versions the packages/features/ee and apps/api/v2/src/ee directories still require a subscription for production use. The relicence covers code from the commit that introduced it onward.
The third is planning a self-host around teams. Anyone who stands up Cal.diy expecting shared availability and routing forms discovers they are missing only after configuring the database, the mail server and the OAuth applications, which is several hours of work in.
The fourth is pinning @calcom/atoms to the highest version number. Versions 2.7.0 through 2.11.0 carry higher numbers than latest but are marked deprecated. The correct entry is "@calcom/atoms": "2.6.7" or a range that does not go past the 2.6 line.
The fifth is importing through a subpath from @calcom/atoms. The exports field promises ./availability/AvailabilitySettings and ./timezone, but the files field does not publish those directories and the archive does not contain them. Everything that actually works comes out of the main entry point.
The sixth is filling in all 174 variables from the example file. Three are required and two more have sensible defaults. The rest configure integrations that most deployments never switch on.
The seventh is a mismatch of secrets between building the image and running it. The README attaches the note Must match build variable to NEXTAUTH_SECRET and CALENDSO_ENCRYPTION_KEY, and the symptom of a mismatch is users being logged out right after signing in.
The eighth is reaching for NODE_TLS_REJECT_UNAUTHORIZED=0 as the first fix for problems behind a load balancer. The README does offer it for SSL termination at the edge, but marks it as something you do at your own risk, because it disables certificate verification across the whole process.
FAQ
Is Cal.com still open?
What is open is the calcom/cal.diy fork under MIT, with the company features removed. The commercial product running in the cloud has no public repository, and the new project's README points you at sales for on-prem access. Until spring 2026 the whole thing lived in one repository split between AGPLv3 and a commercial licence.
When do the AGPL obligations trigger on versions from before the relicence?
When you modify the code and make it available to users over a network. An unmodified instance requires you to publish nothing, because the matching sources are already available. A modified one requires you to offer the sources of your version to the people using it through a browser. A host page embedding booking in a frame is a separate work, but the embed script served from your instance is not.
Can I self-host Cal.com with teams and routing forms?
Not from the open repository. The Cal.diy README lists Teams, Organizations, Insights, Workflows and SSO with SAML as removed, and the database schema on main contains neither a Workflow model nor a routing forms model. The Team and Membership tables remain in the schema, which does not mean an interface for them exists.
How many environment variables do you really have to set?
Three marked as required: DATABASE_URL, NEXTAUTH_SECRET and CALENDSO_ENCRYPTION_KEY. Two more, NEXT_PUBLIC_WEBAPP_URL and NEXTAUTH_URL, have defaults good enough for a local start. The .env.example file has 174 uncommented assignments, but nearly all of them concern optional integrations.
Is an API key enough for the Google Calendar integration?
No. You need your own Google Cloud project, the Google Calendar API enabled, a consent screen configured with the .../auth/calendar.events and .../auth/calendar.readonly scopes, a Web Application OAuth client ID with two redirect URIs, and the full contents of the downloaded JSON file pasted as the value of GOOGLE_API_CREDENTIALS. Microsoft equivalently requires an Azure registration and the MS_GRAPH_CLIENT_ID and MS_GRAPH_CLIENT_SECRET pair.
May @calcom/embed-react be used commercially?
The package declares SEE LICENSE IN LICENSE in the registry, and the file in the archive is the Cal.com commercial licence with a carve-out covering everything served client-side, which falls under AGPLv3. Since the package contains only built client-side code and type declarations, the carve-out covers all of it. That is a reading of the text rather than an official interpretation, so on a high-stakes deployment confirm it with the vendor.
You will find the source code in the Cal.diy repository, the hosted pricing on the Cal.com site, and the booking embed packages in the npm registry.