# BookingPro
A Calendly-style booking widget for Framer. Visitors pick a date and time on your own site and submit their details — fully branded, on your own domain, with no monthly subscription. Drop it in, paste your Formspree ID, optionally connect a Google Sheet that holds your taken slots, and you're live.
---
## Why You'll Like It
- Calendly-style booking without the monthly fee. A polished date+time picker that lives on your own domain, with your own brand. One-time purchase instead of a per-seat subscription.
- Friendly booking horizon. Pick "1 month from today", "3 months", "1 year" — no need to think in raw days. Calendar math is correct (1 month = the same day next month, not 30 days).
- Pick your week start. Monday (Europe / ISO, default), Sunday (US), or Saturday (Middle East). The grid and weekday labels rotate automatically.
- Four booking modes built in. Single appointment, multi-time block, hotel-style date range (check-in / check-out), or multiple independent dates. One component, all the use cases.
- Three ways to know what's already taken. Edit the booked list right in Framer, or sync from a Google Sheet, or pull from any JSON URL.
- Already-booked slots are visually clear. Taken slots are grayed out and show your custom message — no double bookings.
- Truly customizable form. You decide which fields to ask: name, email, phone, project description, anything.
- Sends straight to Formspree (or any webhook). The chosen date(s) and time(s) are added to the payload automatically.
- Designer-first. Four ready-made presets (Modern, Minimal, Soft, Dark), every color editable, every label rewriteable. Korean / Japanese / Spanish weekday and month names just need a quick paste.
- No code, no plugins, no CMS. No CMS dependency — booking data comes from your panel or a URL you choose.
---
## Install Into Your Framer Project
After purchase you'll receive a `.txt` file containing the component URL. To bring it into Framer:
1. Open the `.txt` file and copy the URL (Ctrl + C / Cmd + C).
2. Go to your Framer dashboard and open the project where you want to use it.
3. Click into the canvas.
4. Paste the URL directly onto the canvas (Ctrl + V / Cmd + V).
Framer will detect the link and place the component on your page. Drag it, resize it, and configure it from the right-side property panel.
---
## Four Booking Modes
Pick one in the Booking Mode dropdown at the top of the property panel.
### Date + Single Time
Classic Calendly-style. User picks a date, then one time slot. Best for: 1-on-1 calls, consultations, demos, photo shoots.
### Date + Multiple Times
User picks a date, then any number of time slots in that day. Optional Consecutive Only toggle requires the picked slots to be adjacent — perfect for "book a 2-hour studio block" or "book lunch + the meeting after".
### Date Range (Check-in / Check-out)
Hotel-style. User picks a start date (check-in), then an end date (check-out). The whole range highlights, the form summary shows the night count. Best for: lodging, multi-day rentals, conference room reservations.
### Multiple Dates
User picks any number of independent days. Best for: "select all the dates you're available", workshop signups with optional days, photo session day picker.
The booked list works for all modes. In date-only modes (range / multi-date), any taken date in your sheet blocks that whole day.
---
## Setup in 60 Seconds
1. Drop the component on your canvas.
2. Open the property panel on the right.
3. Availability — set your working days, hours, and slot length (e.g. Mon–Fri, 9–18, 30-minute slots).
4. Form Fields — add the questions you want to ask the visitor.
5. Submission — pick Formspree, paste your form ID. Done.
Optional but powerful: set Existing Bookings → Source to Google Sheet and paste a published CSV URL so booked slots come straight from your spreadsheet.
---
## What You Can Customize
Every group below is a collapsible panel in the Framer property pane.
- Style Preset — pick a look in one click (Modern / Minimal / Soft / Dark / Custom).
- Availability — working days (1=Mon, 2=Tue…), hours, slot length, lunch break, holidays, timezone label.
- Existing Bookings — pick how to sync taken slots: manual list, Google Sheet, or JSON URL.
- Form Fields — add any number of fields with id, label, type, placeholder, and required flag.
- Submission — Formspree ID, Webhook URL + Allowed Domains, email subject, the keys used for date/time in the payload.
- Messages — every visible string. Translate to any language, change the booked-slot warning, the success message, the back/submit labels — anything.
- Calendar Style — header, weekday labels, date colors, today ring, booked-slot dot color.
- Slots Style — slot colors (default / hover / selected / booked), columns, padding, radius.
- Form Style — input colors, label color, font sizes, spacing.
- Buttons — primary and secondary button colors, radius, padding, weight.
- Container — background, border, radius, summary pill, progress bar.
Most designers only touch a few of these. Pick a preset, edit your fields, ship.
---
## Three Ways to Track Booked Slots
You decide where the list of "already taken" times lives.
### 1. Manual List
Type slots into the property panel. Best for solo founders or low-volume use.
### 2. Google Sheet (CSV)
Step 1 — Create the sheet. Open a new Google Sheet and add two column headers in the first row: `date` and `time`. Then list each booked slot as one row:
| date | time |
|------|------|
| 2026-05-15 | 10:00 |
| 2026-05-15 | 14:00 |
| 2026-05-16 | 11:00 |
Format rules:
- date in `YYYY-MM-DD` format (e.g. `2026-05-15`)
- time in 24-hour `HH:MM` format (e.g. `10:00`, `14:30`)
- One booking per row
Step 2 — Publish the sheet as CSV. In Google Sheets:
1. File → Share → Publish to web
2. Choose your sheet, format Comma-separated values (.csv), click Publish
3. Copy the URL Google gives you
4. Paste into the Sheet CSV URL field in the property panel
The component re-checks the sheet on load (and optionally every N seconds). Add or remove a row in the sheet and the website updates without redeploying. Beautiful for a small team where someone manages bookings in Sheets.
For date-only modes (Date Range / Multiple Dates): the `time` column is still required, but the value is ignored — you can put anything (e.g. `00:00`). One row per blocked day.
### 3. Custom JSON URL
For anyone with a backend or automation. Point it at any URL that returns JSON like:
```json
[
{ "date": "2026-05-15", "time": "10:00" },
{ "date": "2026-05-15", "time": "14:00" }
]
```
or `{ "bookings": [...] }` or `{ "data": [...] }`. The component handles all three shapes.
---
## What the Booked Slot Behavior Looks Like
When a slot is already taken:
- It appears grayed out in the time grid
- The clickable behavior depends on Disable Booked Slots:
- On (default): the slot can't be clicked at all
- Off: clicking it shows your custom Booked Slot Message ("Sorry, this time is already taken — please pick another")
You write the message in any language you want, with any tone. The default is friendly: "This slot is already taken. Please pick another time."
---
## Where Your Data Goes
When the user submits, the component sends a JSON `POST` to your endpoint with all the fields they filled in plus the chosen date and time.
### Formspree
Endpoint: `https://formspree.io/f/{your-form-id}`. You only paste the form ID (e.g. `xnqklbpr`) — not a full URL. The component builds the HTTPS endpoint for you.
### Webhook (POST)
Any URL that accepts a JSON body — Zapier, Make, n8n, your own server, Notion via integrations, Airtable, Slack incoming webhooks. Two rules:
1. HTTPS only. `http://` URLs are rejected.
2. List the allowed domains. In the Submission group, fill Allowed Domains with a comma-separated list of the hosts you trust, e.g. `hooks.zapier.com, hook.us1.make.com, your-backend.com`. The component refuses to POST anywhere else. Subdomains count (so `hook.us1.make.com` covers `*.hook.us1.make.com` as well).
This keeps visitor data safe: if a webhook URL is ever changed by mistake (or by someone you didn't authorize), submissions won't silently leak to an unknown server.
### Payload Shape
The exact keys depend on the Booking Mode you chose. Custom form field IDs always come through. Here are the extras the component adds automatically:
Date + Single Time (default)
```json
{
"name": "Sarah Kim",
"email": "sarah@example.com",
"date": "2026-05-15",
"time": "14:00",
"_subject": "New booking request"
}
```
Date + Multiple Times
```json
{
"name": "Sarah Kim",
"email": "sarah@example.com",
"date": "2026-05-15",
"time": "14:00",
"times": "14:00, 14:30, 15:00",
"_subject": "..."
}
```
Date Range (Check-in / Check-out)
```json
{
"name": "Sarah Kim",
"email": "sarah@example.com",
"date": "2026-05-15",
"checkOut": "2026-05-18",
"nights": "3",
"dates": "2026-05-15, 2026-05-16, 2026-05-17, 2026-05-18",
"_subject": "..."
}
```
Multiple Dates
```json
{
"name": "Sarah Kim",
"email": "sarah@example.com",
"date": "2026-05-15",
"dates": "2026-05-15, 2026-05-22, 2026-06-05",
"_subject": "..."
}
```
All key names (`date`, `time`, `checkOut`, `nights`, `dates`, `times`) are configurable in the Submission group, in case your tool expects different names.
If your endpoint replies with a non-2xx status, the component shows your Error Message with a Back button so the user can try again.
---
## Great For
- 1-on-1 consultations and discovery calls
- Coaching sessions and tutoring
- Photo / video shoot booking
- Studio / equipment rental
- Salon / clinic appointments (with the holiday list filled in)
- Hotels, B&Bs, and lodging with check-in / check-out (range mode)
- Multi-day rentals and tour bookings
- Workshop / class registration with optional days (multi-date mode)
- Internal demos and sales calls
- Real estate viewings
- Anywhere you currently bounce between emails to schedule, or pay $10–30/month for a tool you barely use
---
## Power It Up With Your Favorite Tools
BookingPro sends a clean JSON payload to Formspree or any webhook, so it slots straight into the workflow you already have:
- Zapier / Make / n8n — Auto-send a confirmation email to the visitor, push the booking into Google Sheets, create a Notion page, or message a Slack channel.
- Google Calendar / Outlook — Add a Zapier step to create a calendar event the moment a booking lands.
- Gmail / SendGrid / Resend — Trigger a branded confirmation email from your own address.
- Stripe / PayPal — For paid bookings, wire the webhook to a payment link and unlock the calendar event after checkout.
- Your own server — Point the webhook at any HTTPS endpoint that accepts a JSON `POST`. Total control.
Because the data comes through as a regular `POST`, every "what if I want X to happen automatically" question has an answer.
---
## A Few Tips
- For consultations, set Slot Length to 30 or 60 and keep Working Days to weekdays only.
- If you allow same-day bookings, set Min Days Ahead to 0. To require a day's notice, set it to 1.
- Use Holidays for fixed off-days (e.g. `2026-12-25, 2026-01-01`).
- Korean labels: paste `1월, 2월, 3월, 4월, 5월, 6월, 7월, 8월, 9월, 10월, 11월, 12월` into Month Labels and `월, 화, 수, 목, 금, 토, 일` into Weekday Labels.
- For Google Sheet sync, keep your sheet really simple — just `date` and `time` columns. Anything else is ignored.
- Test once with Submission → Send To = None to walk through the flow without sending anything.
---
## Need Help?
If a submission isn't arriving, check that:
- The Formspree ID is just the ID (e.g. `xnqklbpr`), not the full URL.
- Formspree has confirmed your sender email (they email you once after the first submission).
- Your webhook (if using one) is HTTPS and the host is listed in Allowed Domains (comma-separated, e.g. `hooks.zapier.com, hook.us1.make.com`).
- Your webhook responds with a 2xx status.
If you see a red error banner on the Framer canvas:
- That banner shows up only while you're designing — it's a heads-up that the Google Sheet / JSON URL didn't load. Visitors will never see it.
- Click Retry to re-fetch without touching the property panel.
- Common cause: the Sheet wasn't published as CSV, or the JSON URL returned a non-2xx status.
If booked slots aren't grayed out:
- The Sheet URL must be the published CSV URL (ends with `output=csv`), not the regular sheet URL.
- The column headers in your sheet should match what you typed in Date Column and Time Column (default `date` and `time`).
- Dates should be in `YYYY-MM-DD` format. The component also accepts `MM/DD/YYYY` and `YYYY/MM/DD` but `YYYY-MM-DD` is safest.
Everything else works out of the box.