A booking page: you publish your availability, someone picks a slot, and both calendars get the event. It is the open-source answer to Calendly, and the hosted plan is what you pay for rather than the software.
Build me a booking page that replaces a hosted Cal.com: availability, a link people can book from, and both calendars updated. Read this first: the product you are replacing is open source, and self-hosting it is a real answer — the hosted plan is what you pay for, not the software. Build this instead when your booking needs are one or two event types for one or two people, which is most people's. STACK - Node 20+ with Fastify, server-rendered HTML with a little vanilla JS - SQLite through better-sqlite3, WAL mode - Caddy in front THE DATA MODEL - users: id, name, slug, email, timezone, created_at - event_types: id, user_id, slug, title, description_md, duration_minutes, buffer_before, buffer_after, min_notice_minutes, max_days_ahead, max_per_day, location_kind, location_value, price_cents, is_active, requires_confirmation - availability_rules: id, user_id, schedule_id, weekday, start_local, end_local - schedules: id, user_id, name, timezone, is_default - date_overrides: id, user_id, date, is_unavailable, start_local, end_local — holidays and one-off changes - bookings: id, event_type_id, uid, starts_at_utc, ends_at_utc, attendee_name, attendee_email, attendee_timezone, answers_json, status, cancel_token, reschedule_token, external_event_id, created_at, cancelled_at, cancel_reason - calendar_connections: id, user_id, provider, credentials_encrypted, calendar_ids_json, write_calendar_id, sync_token, last_synced_at - busy_blocks: id, connection_id, starts_at_utc, ends_at_utc, source_event_id, fetched_at — only intervals, never the events' details - workflows: id, event_type_id, trigger, offset_minutes, channel, template_md TIME, WHICH IS WHERE HOMEMADE SCHEDULERS DIE - Every instant stored in UTC. Availability rules stored as local times with a named zone, because 09:00 in Europe/Rome is a different instant in July and in January - Use a real time zone library and the IANA database; never an offset - Daylight saving: a rule crossing a transition must produce the right slots on both days, and the test suite includes a spring-forward day, an autumn-back day, and a zone whose rules changed - The attendee sees slots in their own zone with the organiser's zone shown too - Whole-day and multi-day concepts are dates, not midnight instants AVAILABILITY - Weekly rules per schedule, several schedules per person, one attached to each event type - Date overrides for holidays and exceptions - Buffers before and after, minimum notice, a maximum horizon, and a daily cap - Slot generation: take the rules, subtract busy blocks, subtract existing bookings with their buffers, apply the notice and the cap, and round to a sensible increment. One pure function, heavily tested, with no database access inside it CALENDARS - Two-way with Google and Microsoft through their APIs, and read-only ICS for anything else including Apple - Incremental sync with the provider's sync token, plus a full reconcile on a schedule, because incremental sync drifts - Recurring events expanded properly — recurrence rules with exceptions are what break naive implementations, and a wrongly expanded weekly meeting means double bookings for a year - Write the booking to a chosen calendar with the attendee invited, and delete or update it when the booking changes - Store only busy intervals from the read side. The organiser's meeting titles are none of this tool's business BOOKING - A public page per event type: pick a day, pick a slot, fill the questions, confirm. Three screens, no account - Custom questions per event type, required or not, validated on the server - A hold on the slot while the form is filled, released after a few minutes, so two people cannot take the same slot in the same minute. The final insert also checks availability inside the transaction — the hold is a courtesy, the transaction is the guarantee - Confirmation required as an option, with an approve and decline path for the organiser - Cancel and reschedule from long random tokens in the email, with no account NOTIFICATIONS - Confirmation to both sides immediately, with an ICS attachment and add-to-calendar links built by hand - Reminders at configurable offsets before, and a follow-up after - Every mail records that it was sent, so nobody is reminded twice - One-click cancellation link in every one of them LOCATIONS - In person with an address, by phone with either side calling, a link you paste, or a meeting created in a conferencing tool if one is connected - The location is in the calendar event and in every email OPERATIONS - .env: DATABASE_PATH, BASE_URL, ENCRYPTION_KEY, SESSION_SECRET, SMTP_URL, GOOGLE_*, MICROSOFT_* - Migrations on boot, each once - Nightly backup off the machine, restore script - Health endpoint that generates slots for tomorrow WHAT MATTERS MOST Slot generation and the booking transaction. Write the pure function and its time zone tests before anything is visible, then have two browsers book the same slot at the same moment and confirm exactly one succeeds. Everything else here is forms and email. Give me the repository, migrations, .env.example, a seed user with two event types, and a README with deploy steps behind Caddy and the OAuth application setup for each calendar provider.
What you lose
- Two-way sync with Google, Microsoft and Apple calendars, including the recurrence rules that break naive implementations
- Time zones handled correctly across daylight-saving boundaries, which is where most home-made schedulers fail
- Video links created automatically on the provider the invitee expects
- A hosted instance somebody else patches — the software is free, the operating is what the fee buys
If you would rather not build
- Calendly, if you would rather not run anything
- A Google Apps Script bound to a form, for very simple cases
What it costs
as published on their pricing page
| Plan | Billed monthly | Billed yearly | Last read |
|---|---|---|---|
| — | $15/mo | — | — |
Their pricing page is where these came from. Seeing a different price? Tell us.
The escape hatch
open source · no votes, no paid placement
Cal.com
$0The product itself, AGPL and self-hostable — the paid plan is the hosting.
calcom/cal.diyfree · open source
Easy!Appointments
$0A PHP booking system that runs on ordinary shared hosting.
alextselegidis/easyappointmentsfree · open source
Why this verdict
our own opinion · changed only by a person
78/100
Verdict yes at 78 rather than higher: the software is already open source, so "build your own" here really means "self-host theirs", and the fee is for someone operating it. Time zones and recurrence are the honest difficulty.
History
tracked since 10 Aug 2026 · nothing is ever overwritten
Questions about Cal.com
answered from the record above
Is Cal.com free?
No — the plan we track is $15 a month. Teams at $15 per user per month billed monthly, $12 annually; the individual plan is free.
Can you replace Cal.com by building your own?
YES. Replaceable in one session with an AI coding agent. Replacement score 78 out of 100, build time one session. Read what you lose before you decide.
How much does Cal.com cost?
$15 a month on Teams — $180 a year. Recorded 9 Aug 2026.
What do you lose by replacing Cal.com?
Two-way sync with Google, Microsoft and Apple calendars, including the recurrence rules that break naive implementations; Time zones handled correctly across daylight-saving boundaries, which is where most home-made schedulers fail; Video links created automatically on the provider the invitee expects; A hosted instance somebody else patches — the software is free, the operating is what the fee buys. If any of those carry weight for you, keep paying.
Is there an open-source alternative to Cal.com?
Yes: Cal.com, Easy!Appointments. The prompt on this page is for when you want it your way instead.
Related entries
same category first, most replaced first
Every week, something stops being worth paying for.
New verdicts, prices that moved, entries added. One email a week. Unsubscribe in one click. Nothing is being sent yet — your address is kept here, and the first issue is the first thing it is used for.
free forever · no tracking pixel · stored here, never passed to anyone

