How to build an appointment booking system with v0
Build a complete booking app in v0 and connect it to a real database. Prompt your way through service catalogs, live slot availability, temporary holds, and guest checkout, all inside your browser.
v0
$ Build the screens for a booking system: a landing page, a service catalog, a service page, a date and time picker, a booking form, a “my bookings” page, and an admin schedule, then connect a database and the routes that make the times real.
- Catalog and picker sketched
- Availability and routes wired
- Ready to merge
What an appointment booking system actually is
A calendar with a till attached. It has to know what you sell, how long each thing takes, when you are open, what is already taken, and what somebody is in the middle of taking, and be right about all five at the second a stranger presses the button.
Most small applications store what people type and hand it back later. This one is different in a way that shapes every decision below: the thing being sold is time, there is exactly one of each slot, and the answer to "can I have Tuesday at three" changes while people are looking at the page. That makes it a transactional application wearing the clothes of a calendar.
It is also why nearly every reader arrives at this page already renting one, and paying for it in one of two shapes. Either you are billed for each person or each calendar that can be booked, or you are billed for how many appointments you take in a month. Both meters are pointed at growth: the first at hiring, the second at being busy.
What makes it genuinely hard to build is not the grid of times. It is that the grid is a conclusion, drawn from your opening hours minus the length of the service minus everything already booked minus everything currently held, and that the conclusion has to survive two people acting on it at once. Get that wrong and you have not shipped a bug, you have double-booked a customer who took an afternoon off work.
The calendar is the easy half
Drawing a month of dates is a component you will finish in an afternoon. Deciding whether the 3pm inside it may be offered is a question about opening hours, service duration, existing bookings, and unfinished ones, and it belongs on the server where all four of those live. Build the answer first and the calendar becomes a way of displaying it.
Two people, one slot, the same second
This is the failure that defines the category. The fix is two-part and neither half is optional: hold the slot while somebody fills in their details, so it stops being offered, and check again at the moment the booking is written rather than trusting what the browser was shown thirty seconds ago.
The appointment you took is not the appointment that happens
Between the booking and the day, people forget, reschedule, and quietly do not turn up. Which of those you plan for (a hold that expires, a reschedule that moves rather than deletes, a policy about cancelling late) is a business decision you make in words before any of it is code.
What happens to appointments once they exist
20.8%
of appointments were no-shows at the primary care network measured in a study published in January 2025 (a network handling over 140,000 visits a month), falling to 10.25% after the changes the paper tracked. That figure is from healthcare, and your own trade may run kinder or harsher, but the shape of it travels: the appointment on your calendar is a promise, not an attendance.
JMIR Formative Research, published January 2025 (read on PubMed Central) · checked study published January 2025
The parts every booking system is built from
One of these six is the product and the other five are around it. If you read only one, read the first: it is where the build time goes and where the embarrassing failures live.
An answer to “is this slot free”, computed rather than assumed
Take the day’s opening hours for that service, cut them into slots the length of the service, remove everything already booked, remove everything somebody is currently holding, remove anything in the past, and what remains is what you may offer. Every one of those five inputs lives on the server, so the answer does too, and the browser gets told, never asked. This is also the piece worth over-building, because it is the only part of the application whose failure is visible to two customers at once.
A service is a duration and a price, not a name
The length of the thing you sell is what decides how the day divides up, so it belongs on the service rather than in a global setting. Once one service runs 30 minutes and another runs 90, the same Tuesday produces two different grids, and a service that is only offered on certain days needs its own opening hours, sitting over your general ones.
The hold, and the customer who wanders off
Somebody picks 3pm and starts typing their phone number. For the next few minutes that slot has to stop being offered to anybody else, which means a hold with a clock on it rather than a booking. Then the harder half: most of those people finish, and some close the tab in a car park. Something has to notice a hold that expired and hand the time back, or your calendar slowly fills with appointments nobody made.
A booking has a life, not a status
Requested, confirmed, moved to next week, finished, called off the night before. Model that as a lifecycle and the awkward questions have answers: whether a customer may cancel two hours ahead, whether a reschedule keeps its history, what a completed appointment unlocks. Model it as a text field and you will be reading your own database to work out what happened.
The customer who will not make an account
A first-time customer with a phone in one hand does not want a password. Taking the booking with an email address is easy. The interesting part is how they come back to it, because a link that works must not be a link that works for everybody. That means a long unguessable token with an expiry, checked together with the email address, rather than a booking number in a URL.
A rating only a real customer can leave
Reviews are worth having precisely because they are hard to fake, so the rule that a review requires a finished appointment of your own is the feature rather than a validation detail. Put that condition in the database and it holds no matter which screen is asking. Leave it in the form and your service pages become a place strangers post.
Build it once or pay per booking
Vendor pricing scales with your team size ($10-$61/mo per seat) or booking volume (free tiers cap at 50-200). Calculate both before choosing.
Build your own
An app you own has neither meter on it. Nobody counts your staff and nobody counts your appointments, and with an AI coding tool writing the availability function, the holds, and the access rules, that build is weeks rather than quarters.
- The fifth person taking bookings costs nothing, so putting somebody else on the rota is not also a software decision
- The four-hundredth appointment in a month costs exactly what the fourth one did
- Your services are pages on your own domain rather than a widget pointed at a vendor’s subdomain
- Every booking, customer, and review sits in your own database, queryable without an export request
- How long a slot is held, how late somebody may cancel, and what counts as available are your rules to write
- Taking a booking from somebody who refuses to make an account is a decision rather than a plan feature
Rent the scheduler
Calendly · Acuity · SimplyBook.me · SetmoreWhat renting buys is a decade of scheduling machinery, and some of it is genuinely tedious to reproduce. Reminders that leave the building, sync with the calendar your staff already keep, the arithmetic of time zones and the weekend the clocks change, taking a deposit at the moment of booking, and intake forms you would otherwise scope one at a time.
- Working on the day you sign up, with the calendar, the reminders, and the clock arithmetic already correct
- Reminders that actually reach the customer, by email and often by text (this template notifies inside the app and sends nothing out of it)
- Money at booking time, a deposit or a card kept on file, which this template has no version of at all
- Sync with the calendar your staff already live in, so a dentist appointment in a personal diary blocks a work slot
- Somebody else’s problem the weekend the clocks change, and every time a customer books from another country
- Every one of the four bills for growth: two per person or calendar, two by how many appointments you take
The default answer when somebody says “send me a booking link”, and the one whose free tier is ruled out by shape rather than by volume: Free gives you “1 event type” and “Connect 1 calendar”, so a catalog of six services does not fit inside it however quiet you are. The page quotes both paid tiers as billed yearly and advertises the annual saving at 16% and 20%, so month-to-month runs higher than either figure. It published no monthly number to our reader, so none is quoted here. Worth knowing what it is built for: meetings between people, rather than a shop window listing what you sell with a price and a length against each item.
calendly.com · checked August 2026
The closest of the four to what this template does, and the one that prices the thing this template gives away: a calendar is a staff member or a room, and you buy them in blocks of 1, 6, and 36. No free tier exists, only a “7-day trial, no credit card needed”. Its features page is also the honest list of what you are choosing not to have: “automated email confirmations, updates, and reminders (*SMS reminders available on Standard plan+)”, “Sync personal and business calendars to avoid double-booking”, and “Collect prepayment, partial deposit, or securely store credit card details at booking”. The first and third of those are additions on a build you own.
acuityscheduling.com · checked August 2026
Included because it states the per-appointment model more plainly than anybody: the plan ladder is a monthly booking cap, so the bill is a function of how busy you were. Do that multiplication before comparing seat prices: 500 appointments a month is a small salon, and it is the middle rung. The second meter is the one people meet later: “custom features” are capped too, at 1, 3, 8, and unlimited, and features here means things like an intake form or a cancellation policy. Prices are quoted in euros because that is the currency its page served us. The page shows annual billing saving up to 17%.
simplybook.me · checked August 2026
The most generous free tier in the set, and the clearest illustration of the argument: it stops at “200 appointments” a month, which is about seven a day. That is the point at which a service business is working rather than starting, so the free plan ends exactly when the thing you built it for begins. Free covers up to four users with email confirmations and reminders and a branded booking page. Pro lifts the caps and adds text reminders and two-way calendar sync. The page’s annual figure is $5 per user a month and its month-to-month figure is higher, without stating the basis plainly enough to quote.
setmore.com · checked August 2026
Rule of thumb: if several people work a rota, money changes hands at booking time, and you would rather never think about a reminder or a time zone again, rent. Those are real products and none of them is a weekend of describing. If the shape of your problem is that your own site should show what you offer, at your prices, with your rules about cancelling, and you would like the bill to stop growing every time you get busier, that is the half worth owning. Two findings worth weighing whichever way you go. In an MGMA poll of 622 medical groups published in January 2025, 42% charged a no-show fee, and those that did reported more improvement in their no-show rates over the year than those that did not, 25% against 16%. And in a second MGMA poll, published in July 2025 with 244 responses, 71% of practices had fewer than a quarter of their patients self-scheduling at all. Offering online booking and having it used are two different projects, and the second one is mostly about telling people it exists.
Why build with v0
v0 starts with the screens: forms, tables, dashboards, whatever the interface needs, generated in React and Next.js from a plain description. That’s the part most people notice first, because it’s fast and it looks finished immediately.
The backend isn’t automatic in the same way. v0 can write it too, meaning Next.js routes and server logic that read and write real data, but you ask for it, usually once the UI already exists and there’s something real to connect it to:
Sketch
Describe the screen or component you want.
Preview
See it rendered live, and select any part of it to adjust directly.
Connect
Add a database and the routes that read and write to it, once the UI needs somewhere real to save.
Iterate
Prompt again for the next screen or the next piece of logic.
The order matters here more than with a full-app builder: v0 gets you a finished-looking interface fast, and the data underneath doesn’t show up until you ask for it and give it somewhere to live.
One clickconnects a database
Supabase, Neon, and Upstash are integrations you add from the project menu once a screen needs to hold onto something real, and v0 provisions the credentials and writes the routes that use them.
Live preview is the feedback loop
Every prompt updates the working interface right there in the chat, so you see the actual screen changing instead of imagining it from a description.
Design mode edits without a prompt
Select an element in the live preview, adjust its style directly or type a plain-language instruction, and v0 applies the change back to the real source code as a new version.
Pay a developer, or do it with AI
Commercial booking tools bill for either the people on your schedule or the bookings they take. Calculate your team size and expected volume, because those are the two lines that drive your bill in year three.
Hire a developer
Custom build, from scratch- Developer
- ~$12k-$46k
- Supabase (backend)
- Free tier · $25/mo (Pro plan)*
- Hosting
- $0 free tier
- Build time
- ~230 hrs of their work
~$12k-$46k to build, then from $25/mo after launch
Our ~230-hour estimate at market rates. The survey linked below puts senior US developers at $100-$150+ an hour and records agencies charging 20-40% above the freelancers they compete with, which is what makes $50/hr and $200/hr the honest ends of the range rather than a wide guess. The part worth reading twice is where those hours sit: availability, the holds that keep a slot from being sold twice, and the safe way a guest returns to their own booking account for roughly a third of them, and not one of the three shows up in a screenshot. That is the usual reason a quote for “a booking page” comes back at several times what the mockups suggest.
Build it with v0
From scratch, with v0- v0
- $0 ($5/mo of credits) to $30+/month (Plus)
- Backend (Supabase)
- Free tier · $25/month (Pro plan)*
- Hosting (Vercel)
- Free on Vercel’s Hobby plan
- Your time
- ~110 hrs
Free to try the idea, ~$30+/month on Plus while you build a real one, then whichever plan you keep using
v0 bills in dollars of credit rather than a flat fee, so cost tracks usage: importing the pre-built template can fit inside Free’s $5 a month, while a from-scratch build runs into Free’s 7-messages-a-day cap quickly and usually needs Plus, at $30 per user per month with no annual discount shown. Because v0 generates the interface and the backend as separate steps rather than one pass, expect more prompts to reach the same result than a tool that writes both together.
* The free-tier line that matters here is the pause, not a storage number. Free gives you a 500 MB database, 1 GB of file storage, and 5 GB of egress a month, and bookings and services are small enough that only the photographs of what you offer will ever push at those. The pause is different: a free project stops after a week with no activity, and a booking page that has gone to sleep is a business that looks closed, which lands squarely in the week after launch, before anybody has found you. From $25/mo, Pro removes the pause, raises egress to 250 GB (then $0.09 per GB), and keeps a daily backup for 7 days.
The two meters a rented scheduler runs are the ones missing from this table. One charges for every person whose diary can be booked, the other for every appointment that gets taken, and both grow as the business does. Neither column above has either, so your busiest month costs exactly what your quietest one does, and the only line that moves is whichever AI model you decide to call. That shape suits a business whose bookings are going up, which is the only kind of business anybody is trying to build.
Prices and rates from supabase.com, developex.com, v0.app, v0.app and vercel.com, checked August 2026.
Decide before you build
Six things to settle in writing first. Every one of them is a promise to somebody who is about to arrange their day around you, and each costs far less to decide now than to unpick once real appointments are in the calendar.
Whether somebody can book without an account
Settle this before the schema, because it reaches into every table. An account gives a customer their own history, where most follow-up questions answer themselves, but demanding one from a first-timer holding a phone loses the booking outright. The middle path: take the appointment from any email address, then let them claim it later.
What available actually means in your business
One diary, or one per person? A room that hosts one thing at a time, or a class with twelve places? This shapes the availability function, and honesty beats ambition here: a single shared calendar with your real opening hours is a working product, while a half-built per-staff model produces double bookings.
How long you hold a slot, and what happens when it expires
Too short and you lose bookings from people who type slowly. Too long and your quietest-looking Tuesday is full of ghosts. Fifteen minutes is a reasonable start. The half people forget is the expiry: something has to sweep up abandoned holds, and a customer coming back to a lapsed one thinks they have an appointment.
Your cancellation and no-show policy, in words first
How late may somebody cancel and still be fine? What happens if they simply do not arrive? Write the sentences in the language you would use to a regular customer, and the software becomes a question of where to show them. One caution: a policy that mentions a fee needs a way to charge it (see 06).
Whose clock the appointment is in
Almost every booking bug that survives to production is a time-zone bug. Store the moment in one absolute form on the server, decide which zone your business runs in, and show it in the customer’s own. Then test the awkward cases on purpose: a customer two zones away, and the weekend the clocks change.
Whether money changes hands at the booking
Recording an appointment and charging for it are separate projects, and shipping the first without the second is fine. Most small businesses take payment on the day. Decide deliberately, though: a deposit changes what a cancellation means, needs a refund path, and brings a payment provider into the security decisions below.
Comparing your build options
A booking system is a shop window with a machine behind it, and where you start decides which of the two eats your evenings. Three routes to the same product: written out by hand, assembled on a UI kit with nothing underneath, or generated in v0, where the window arrives first and the machine follows when you ask for it.
The screens are not what takes the time. Working out whether a slot is free means reading your opening hours, the length of the service, everything already booked, and everything somebody is halfway through booking, and then being right about it while two strangers press the same button. That part is a day of thinking and a week of edge cases.
A starter kit hands you a calendar, a form, and a dashboard shell, which feels like most of the job and is the part you would have finished anyway. Nothing in it knows what you sell, how long it takes, when you are open, or that the 3pm on screen was taken ninety seconds ago. All of that is still yours to write.
The picker appears the moment you describe it. The database and the routes deciding which times it may show up with arrive once you ask. The same work as the rows above, done in two passes rather than one, each chat landing on a branch of its own.
Estimate your exact build timeframe
Not every project needs all six features. Uncheck the options your business doesn’t require, like guest checkout or online payments, to calculate your real build time.
Your estimate
110 hrs
start to finish
Based on the 6 of 6 features you’ve selected, plus ~26h of groundwork. Toggle any on the left to watch the number move, and open the groundwork row to untick what you have already, such as a database that is already running or going live if you are only building a mock-up for now.
A rough estimate, not a quote. Real time depends on how much you customize and how clean your data is.
Let’s set up the tools you need
v0 runs entirely in the browser too, so there’s no download and no terminal. Before step 01: sign up, pick a plan, and know that a database is one click away once a screen actually needs to save something. GitHub is the last piece, and it’s what turns the chat into a project you can hand to someone else.
v0 account
Sign in with a GitHub, Google, or email account and you land in a chat where you describe the interface you want. There’s nothing to install.
Sign up for v0v0 subscription
Free includes $5 of credits a month, capped at 7 messages a day, which is fine for trying a few screens and thin for a real build. Plus is $30 per user per month, billed monthly only (v0’s pricing page shows no annual option), and it’s the realistic floor once you’re iterating past a handful of screens.
Compare v0 plansDatabase (Supabase, Neon or Upstash)
Where your project keeps its data, added when a screen needs one. v0 generates the interface first. A database is a one-click integration you add from the project menu once a screen needs somewhere real to save to, with Supabase, Neon and Upstash as the options, and connecting one lets v0 write the routes that use it.
Connect a database in v0GitHub connection
Not needed to build anything, since v0 keeps its own history of every change. Connect a repository from the chat’s Git panel and v0 also commits every code-changing message to its own branch, ready to merge as a pull request. That is the step that turns the chat into a project other tools, and other people, can use.
Connect v0 to GitHubThe database step is the one worth not skipping once your screens need real data. Everything else here takes a couple of minutes, and after that you’re just describing what you want.
Build your booking system, one chat at a time
Build UI screens first, then handle database rules in separate prompts. AI design tools excel at generating interfaces quickly, but complex logic, like live slot availability and double-booking protection, requires dedicated prompts to run securely.
- 01
Sketch the catalog and the picker, because that is what v0 leads with
The whole public side with invented services and invented times, in one chat. The point is to look at how somebody gets from a service to a time before anything is stored anywhere.
PromptSketch the booking screensBuild the public screens for an appointment booking system in Next.js, using placeholder content for now. A landing page. A catalog of services with category filters, each card showing a price and a length. A service page with photos, the description, and a reviews section. A booking screen with a date picker beside a list of times for that date, then a details form and a confirmation page. A simple “my bookings” page. Include one 30-minute service and one 90-minute one so I can see how differently their times lay out, and show me the empty state for a day with nothing free. Keep it responsive. Most people will book this from a phone.
Use Design mode for the visual corrections rather than describing them. On a booking page the tap targets on the time list and how the picker reads on a phone are most of the product, and those are exactly what Design mode is for.
- 02
Connect the database and let the catalog read real services
Where the hand-off happens. Add the integration from the project menu, then ask for the schema and the routes that will retire the placeholders you just looked at.
PromptModel the catalog and connect the databaseA Supabase integration is now connected to this project. Design the schema and the routes that read it, then take the placeholder content out of the screens you already built. Services with a title, description, category, price, one or more durations in minutes, an image, and optional opening hours of their own. A settings table holds my default weekly opening hours, the slot length, the hold length in minutes, my currency, and my cancellation policy as text. Seed eight services across three categories with mixed lengths (one 30-minute, one 90-minute, one offered on two days a week only) and opening hours with one day closed. Write server-side routes for the catalog and a single service, and wire the existing pages to them. Nothing writes yet, and the times in the picker are still placeholders until the next chat.
Get this done before another screen exists. From here on everything reads or writes a real row, and a picker designed against invented times has to be rebuilt the moment the real ones turn out to arrive in a different shape.
- 03
Ask for availability in the database, not in the route
The step that decides whether the times on screen mean anything, and the one where a tool that leads with interfaces will happily hand you something that looks like availability and filters an array.
PromptWork out availability properlyStart a new chat for availability. Build it as a database function, not in JavaScript in the route: given service ids, a date range, and the duration the customer picked, it returns the slots that are genuinely free. For each day, take that service’s own opening hours where present and my defaults otherwise, cut the day into slots of the requested duration, then remove anything in the past, anything overlapping a booking with status confirmed or pending, and anything overlapping a hold that has not expired. Appointments are stored as absolute moments. Expose it through one server-side route the picker calls, and wire the real times into the screen from step 01. What I am asking you not to do is fetch bookings and filter them in the route or the component. The overlap logic belongs in the database, next to the rows it is comparing. Then show me the working for one specific Tuesday, listing which slots each rule removed and why.
Say “in the database, not in the route” and then check the diff for it. Filtering a fetched array is the natural thing to write when the interface came first, and here it is worse than slow: the array you fetched is a snapshot, and the whole point of this function is to be right at the moment somebody presses the button.
- 04
The booking as its own chat: holds, guests, and the race
The first part of this build that writes rather than reads. Give it a chat to itself, because at some point you will restore an earlier version here and you would rather the catalog did not travel back with it.
PromptTake the booking, with a hold behind itNew chat for the booking side. A booking records the services and durations chosen, its absolute start, a status of pending, confirmed, completed, or cancelled, its customer or a guest name and email, notes, and a hold expiry. Choosing a slot writes a pending booking held for the minutes set in my settings, which removes it from availability immediately. Confirming makes it confirmed and clears the hold. Inside that write, call the availability function again and refuse the booking if the slot has gone, returning a message rather than an error. Add a cart so several services become one appointment long enough for the total. For guests: a name and an email, a long random token with an expiry generated on the server, and a lookup route requiring the token, the email, and the booking id together, never a booking id alone in a URL. Then demonstrate two requests confirming the same slot at the same moment, with one of them refused.
The guest lookup is the one to be firm about. A URL carrying only a booking id is the default shape for this feature and it is a page anybody can walk through by changing a number, which on this app means somebody else’s name, phone number, and where they are going to be on Thursday.
- 05
Roles and access rules, in a separate chat and its own pull request
Two kinds of account over the same tables, plus two kinds of visitor with none. Its own chat again, and its own reviewable diff.
PromptAdd auth, roles, and access rulesNew chat, for authentication and access rules. Add email-and-password sign-up, login, logout, a persisted session, and a profile row per account. Add two roles (customer and admin) in a separate table rather than on the account record, since anything stored there is editable by its owner, with a security definer function to check a role. Then row-level security on every table: an anonymous visitor reads services, the settings the page needs, and approved reviews only. A customer reads and cancels only their own bookings. An admin reaches everything. A guest reaches their own booking only through the token route, so no policy opens bookings to anonymous reads. Explicit with-check clauses on inserts and updates, a review permitted only where a completed booking for that service belongs to that customer, and the service-images bucket public to read and admin-only to write. Then prove four things by querying as a signed-in user: one customer cannot read another’s booking, an anonymous request cannot list bookings, a guest lookup with a wrong token returns nothing, and a review with no matching booking is refused.
Give this its own pull request rather than stacking it on the booking work. Of every change in this build, the access rules are the one you will most want to read back as a single self-contained diff in six months, when somebody asks you what stops a customer seeing the rest of your diary.
- 06Destination
The back office, the sweep, then merge and deploy
The admin screens, the job that clears abandoned holds, a full day worked through the app, and only then the merge that publishes it.
PromptAdd the back office, the sweep, and rehearseLast chat. The admin side: a schedule by day and week, bookings I can add and cancel from it, a bookings manager filtered by status and service, a reschedule that moves an appointment and keeps its history, a cancellation window read from my settings, a service editor with photo upload and per-service opening hours, a settings screen, review moderation, in-app notifications on a new or moved booking, and a dashboard charting bookings and value booked by service. Then the sweep: a job that cancels pending bookings whose hold has expired, authenticated by a shared secret rather than a session, safe to run twice, and tell me where that schedule has to live given this app deploys to Vercel, rather than assuming it can live in the app itself. Finally, walk a whole day with me: book as a stranger, abandon a booking and watch the slot come back once the sweep runs, follow a guest link, reschedule as a customer, try to cancel too late, add a walk-in from the schedule, moderate a review, and check the dashboard agrees.
Ask where the schedule lives rather than assuming. It is the one piece of this build that is not a request from a browser, and the piece most likely to be quietly left out and noticed weeks later, when a Tuesday that looks fully booked turns out to be four holds from people who never came back.
Protect the calendar and your customers
A booking entry holds more than a name: it reveals where a customer will physically be at a specific hour. What follows are the database-level rules and server-side policies that keep every record strictly isolated and protected.
Standard Password Protection
Sign-up, sign-in, sessions, and password resets all belong to whatever auth service ships with your database. Writing them yourself buys you nothing, and when a hand-rolled one is wrong it is wrong silently. Keep the method dull as well: your customers are booking a haircut rather than opening a bank account, and an unfamiliar sign-in standing between somebody and the button is a booking you never took.
Separate Admin and Customer Access
The template ships customer and admin, which is genuinely all a single-location business needs. What makes the rules interesting here is the other two cases: an anonymous visitor who may read your catalog and nothing else, and a guest who has no account but must reach exactly one booking: their own. Most access-rule mistakes in this application happen in that second case rather than between the two signed-in roles.
Automatic Double-Booking Protection
This one is the security item people file under correctness. If the page posts “this slot is free” along with the booking, then the way to book a slot that is not free is to change what the page posts. The server has to re-derive availability from the opening hours and the existing bookings at the moment it writes the row, and refuse the write if the answer has changed since the customer was shown it.
Data Rules Protected from Tampering
A list of appointments narrowed in the component is one forgotten condition away from showing a stranger somebody’s name, phone number, and where they will be at 3pm on Thursday. Row-level security asks that question inside the database instead, as the rows are handed over. The reason to move it there is what happens on the day somebody forgets: the query comes back with nothing rather than with your whole diary.
Strict Privacy for Customer Profiles
The template’s migrations declare 68 row-level security policies, and a good number of those are later revisions of earlier ones, which is what tightening a rule looks like in a migration history. The count is structural rather than anxious. Take the bookings table: the customer it belongs to may read and cancel it, a guest may reach it with a token, an admin may see every one, and an anonymous visitor may see none. Four rules on one table, before you reach the other eight.
Private & Secure Booking Links
Letting somebody find their booking without an account means something in that flow stands in for a password. A booking number does not: they are short, they are sequential-looking, and people forward them. The template generates a long random token, gives it an expiry, and checks it together with the email address and the booking it belongs to, so holding any one of the three is not enough.
Bot & Brute-Force Protection
A public endpoint that answers questions about bookings is worth someone’s time to guess at, so the template writes every attempt (successful or not, with the address it came from) to an audit table, and rate-limits by address. Worth knowing where the rough edge is: that rate limit is kept in the function’s own memory, so it resets whenever a new instance starts. If your booking page gets real attention, move it somewhere shared and keep the audit table either way.
Prevent Unauthorized Admin Escalation
Roles want a table of their own for one narrow reason: a customer can edit their own account record. Store the role there and promoting yourself to admin is a profile update, and an admin in this application reads every customer’s name, number, and where they are due to be on Thursday. The template keeps roles in a separate table with a security-definer function to check them, which is the pattern worth insisting on.
Privileged Actions Run Server-Side
Working out availability, holding a slot, confirming a guest’s booking, and sweeping up abandoned holds are each several operations at once, and each needs to be permitted more than the person who set it off. The template gives 34 of these a server-side function each, sitting above the access rules with the rule written into its own code, which is how the policies can stay as narrow as they are without making those four jobs impossible. The key those functions run with stays on the server, never in the app a customer downloads.
Protected Scheduled Jobs
The sweep that releases expired holds is not called by a person, so a sign-in cannot protect it. It gets a shared secret instead, checked on every call, kept with your other server-side secrets rather than in the code, which is exactly how the template does it. A scheduled endpoint with no check on it is a public endpoint that cancels bookings, and it will be found.
Location Data Treated as Personal Data
Most records in most applications are dull. “This person will be at this address at this time” is not, particularly for a clinic or anywhere visited alone. Decide who on your team may read the diary, decide how long you keep past appointments, and decide what a customer gets if they ask you to delete theirs, and note that reviews and no-show history are the parts people forget are attached to a name.
Automatic Daily Data Backups
No backup is taken of your services, your settings, or a single booking in your calendar. Daily backups, kept for a week, start on Supabase Pro at $25/mo. Worth doing before the diary is the only record of next month, because unlike most data in a small business, a calendar cannot be reconstructed from anywhere else. The people in it assume you know when they are coming.
Review the access rules (row-level security policies) on every table. For each one, tell me in simple terms who can view, add, edit, and delete records, confirm that people can only reach their own data while the right roles can reach more, and flag anything left open that shouldn’t be.
Paste this into the chat before launch so v0 checks nobody can see data they shouldn’t.
One rule outranks the rest here: the database service key, any AI provider key, and the secret guarding your scheduled sweep live on the server and nowhere else: not in the app a customer downloads, and not in a public repository. Treat any of the three that escapes as burned, and replace it the same day.
What speeds the build, and what slows it
Speeds the build
- One screen per message, checked in the live preview before you ask for the next one
- Adding a database as soon as a screen needs to remember something, so v0 wires it up as it builds
- Clicking the thing you want changed in Design mode, rather than describing where it sits
- One feature per chat, so its history reads as a straight line you can walk back
- Sending a finished chat over to GitHub before starting the next feature, rather than stacking several together
Slows the build
- Asking for the whole app in one message instead of one screen at a time
- Building screens for data the database does not hold yet
- Describing which button to change when you could just click it
- Letting one chat run for weeks, so going back to an older version undoes everything built after it
- Approving several messages in a row without looking at the preview after each one
Connecting GitHub (Optional)
Every change in v0 is saved automatically without any technical setup. You only need to link GitHub if you want a private copy under your own control or plan to hand the codebase over to external developers.
Every message is a version
Each time a message changes your code, v0 saves it as a new version. Restoring an older one adds it back as the newest version instead of branching, so the history stays one straight line.
Versions, v0 docsUndo from the chat itself
Scroll back through the conversation and click the revert arrow on any earlier reply, or open the version number in the top right to jump straight to a specific one.
Connecting GitHub creates a real repository
From the chat’s Git panel, connect an existing repository or create one. v0 never writes straight to your main branch: every code-changing message is committed to its own working branch first.
GitHub integration, v0 docsA pull request is how it gets merged
When you’re ready, publish and open a pull request from that working branch into your base branch, review it like any other, and merge it. Starting a new chat picks up a fresh branch for the next round of changes.
This can also be v0’s deploy path
If the branch your pull requests merge into is also the linked Vercel project’s production branch, merging one triggers a production deployment. If it isn’t, the merge just follows whatever branch behavior that Vercel project is already configured with.
GitHub integration, v0 docsWhere to host your application
Hosting gives your app a home on the internet so anyone can open it via a web link. Choose a service below to make your site live. (Your database, logins, and business records are stored separately in Supabase, covered below).
| Host | Best for | Notes | Free tier |
|---|---|---|---|
| Vercel | One-click deploys | Point it at the repository once and each push publishes itself, with nothing for a Vite project to configure. The thing to read before a business domain goes near it is the plan terms: Hobby is licensed for personal, non-commercial use, and a page taking money-earning bookings is commercial however small the business is, so Pro, at $20/user/mo. | Pro from $20/user/mo (Hobby is non-commercial) |
| Netlify | Drag-and-drop or Git | Either connect the repository or drop the built folder onto the page, and you are live inside a minute. Add the one redirect rule it asks for: without it a link straight to a single service returns a not-found page, and links to single services are what you will be pasting into replies all day. | Free tier |
| Cloudflare Pages | Customers in other countries | Copies of the site sit close to whoever is loading it rather than in one place. Worth choosing if bookings come from abroad, which is also the situation that makes testing your time handling properly unavoidable. | Generous free tier |
| GitHub Pages | Not really this app | Publishes free out of your GitHub project once you change a routing setting. Here mainly to be ruled out: publishing free means the repository is public, and this code sits beside an application that knows your customers’ names, numbers, and where they will be on Thursday. | Free from a public repo only |
| Firebase Hosting | Teams already on Google | Configure it once and each release is a single command afterwards. Nothing about a booking page argues for it specifically. What argues for it is that your calendar, your mail, and your other accounts are already Google ones. | Free Spark tier |
| AWS Amplify Hosting | Teams already on AWS | Publishes out of the AWS console, and takes the same one rewrite rule so a link to a single service resolves. Pick it when AWS is already on the invoice and adding one more supplier is the part you want to avoid. | Free tier (build + hosting) |
| Surge | Publish from the terminal | A single command sends the built folder online, with no repository involved anywhere. Useful for showing a partner how the booking flow feels before you commit to it. Not for the address that goes in your window. | Free - unlimited publishing |
| DigitalOcean App Platform | DigitalOcean users | Builds and serves out of the account that already runs whatever else you have. One supplier and one invoice is the entire argument, and for a small business that is a real one. | Free - 3 static sites, 1 GB/mo transfer |
Any of them serves the app perfectly well, so judge them on three questions instead of on speed. Whether the plan allows commercial use, since Vercel’s Hobby tier does not and taking paid bookings is commercial by any reading. Whether it publishes from a private repository, because this code sits beside a diary of where your customers are going to be. And whether a deep link resolves in a browser that has never seen your site: a link to one service, and the link a guest follows back to their own booking, both have to land on the right page rather than the home page.
Two things to check on the day you go live, and neither is the hosting. The first is the clock: your server stores a moment and the browser draws it in local time, so open the booking page with your machine set to another time zone and confirm the slot you booked is still the slot that appears. Do it again around a daylight-saving change if your trade runs early mornings. The second is that a static host serves files and runs nothing on a timer, so the sweep that releases abandoned holds has no home there. That job belongs beside the database, and it is the piece most likely to be forgotten until a Tuesday looks full of appointments nobody made.
Keep your data in Supabase
Services, bookings, customers, reviews, your settings, and the photographs of what you offer. This is also where the logic that has to outrank the person asking lives, and where the scheduled sweep belongs.
| Service | Best for | Notes | Free tier |
|---|---|---|---|
| Supabase | Data, auth, functions, files, schedules | The catalog and the calendar sit in Postgres, accounts come from its auth service, the service photographs go into its storage, and its edge functions carry the work that has to be permitted more than the customer setting it off: deriving availability, holding a slot, confirming a guest’s booking. Getting there is opening a free project and giving the app two values, its URL and its publishable key. The piece that belongs here rather than with your host is the timer: the schedule releasing expired holds needs something that runs when nobody is visiting, and a folder of static files is not that. | Free tier, then usage-based |
Where AI genuinely helps a booking business
Each of these is one more prompt once your booking system is running, so copy any of them in as they stand. Every feature reaches the model through a single server-side function, which is the only place your API key ever sits.
Turn “any chance of Tuesday afternoon?” into a bookable slot
The message arriving by text, email, or Instagram is how a lot of appointments still start, and retyping them into your own system is the daily tax. This is the right first feature because it is contained, it saves time immediately, and it never touches the diary without you.
Add a server-side ai function (one place, with my provider key only on it) and a screen where I paste a message from a customer. Send the message to that function and get back a structured guess: which of my services they seem to want, roughly when they are asking for, and the name and contact detail if the message contains one. Then match that guess against real availability and show me the three closest free slots as a draft booking I confirm or discard. Never write a booking directly from the model’s output, and where the message is ambiguous say so rather than choosing for me. If it says “sometime next week” I want to see that, not a Tuesday you picked.
Offer a cancelled slot to whoever actually wants it
A cancellation at 9am for a 2pm appointment is money you can still recover, and the work of recovering it is going through your own list deciding who to ask. That is exactly the sort of judgement a model can draft and you can approve.
When a booking is cancelled, build me a shortlist for the freed slot: customers who asked for that service, whose earlier bookings suggest they are around at that time of day, who have not already got an appointment that week, and who have not asked me to stop contacting them. Send only what my ai function needs to rank them and nothing else: no contact details in the prompt. Show me the list with a one-line reason each, and let me pick who gets the message. Nothing goes out automatically, and anybody who has opted out is filtered before the model sees them.
Draft the awkward reply, with your own policy in front of it
A late cancellation, a reschedule for the third time, a customer asking to be squeezed in on a closed day. The words are the work here, and getting them consistent is worth more than getting them fast.
Add a “draft a reply” action on a booking that sends the booking, its history, and my cancellation policy text to my ai function and returns a short, warm reply I can edit before it goes anywhere. Rules: it may only state what my policy actually says (if the policy is silent on something, the draft says it will check rather than inventing a rule), it never offers a slot without checking availability first, and it never promises a refund or a waived fee, because this application does not take payment and I am the one who decides that.
Turn a month of reviews into a list of things to fix
Ratings tell you the temperature. What you want is the pattern: the same complaint three times, always for the same service, always on the same day of the week.
Add a panel on the admin dashboard that groups the last period’s reviews by what they are actually about, using my ai function, and returns each theme with a count, one representative quote, and which service and which day of the week it concentrates on. Send the review text with customer names removed. State counts as figures rather than describing them vaguely, print the period it covered beside the result, and where a theme rests on one or two reviews say so instead of presenting it as a trend.
Ask your own calendar what went wrong last month
Your dashboard answers the questions you had when you built it. What turns up later is which slots get abandoned mid-booking, which service attracts the no-shows, and whether the people who booked three weeks ahead were the ones who did not arrive.
Add a panel that answers questions about my own booking history through my ai function: no-show and cancellation rates by service, by day of the week, by time of day, and by how far ahead the booking was made, plus how many holds expired without becoming bookings. Have it compute the figures with queries and use the model only to explain what they suggest, so no number is invented. Every answer states the period and the sample size, and where a pattern is thin it says the pattern is thin. Send no customer names or contact details at all.
v0 doesn’t ship its own model for these prompts, so you bring an API key for whichever provider you want, and v0 writes the route that calls it. Start with a cheap model while you’re still iterating on the prompt itself, then swap in a stronger one for the version that ships, and keep every AI feature behind that one route so there’s only one key to rotate.
Get a head start with our template
Every route above begins at an empty folder, which is not the only place a booking page can begin. This one exists, runs, and takes bookings, so the weeks that availability, the holds, and a safe guest path would have cost turn into an afternoon of typing in your own services and your real opening hours.
Appointment Booking System
The exact booking system this guide builds, packaged so you can open it, point it at your own backend, and make it yours from there. A booking platform for service businesses that runs both sides of every appointment. Customers browse your services, see live availability, book their own slot, and leave a review afterward - while you handle the schedule and everything around it from one admin panel.
The key benefits of starting with a template
The availability already comes from your opening hours, and the slot already stops being offered while somebody is finishing. That is most of what this saves you. Behind it, the guest path, the cart, the reviews, the notifications, and the admin schedule are done too.
Building the core from scratch
~110 hrs
Opening the template, already built
~1 hr
~109 hrs of building you skip
Two different measurements, on purpose. Building the application is the ~110 hrs. The ~1 hr is what adopting the built one costs: connect a database of your own, then put in a first handful of services and your real opening hours. Settling your prices, your durations, and your cancellation policy is work whichever path you take, so it sits outside both figures.
Availability that already refuses to double-book
A server-side function works out free slots from each service’s own opening hours merged over your defaults, the slot length you set, the duration the customer picked, and every booking already confirmed or pending, holds included. The browser is told what is free and never asked. Slots are held for the number of minutes you choose, fifteen by default, and a scheduled sweep gated behind its own secret cancels the ones nobody finished.
Guest bookings, done the careful way
A customer books with a name and an email address and no password. Coming back to that booking goes through a long random token with an expiry, generated by a database trigger and checked together with the email address and the booking it belongs to, with every attempt, successful or not, written to an audit table and rate-limited by address. That is the part most builds get wrong on the first pass.
Two roles across 68 declared policies
Customer and admin accounts, with roles kept in their own table behind a security-definer check so nobody can promote themselves, and 68 row-level security policies declared across the migrations as those rules were written and tightened. Around them, 34 server-side functions for the work that has to outrank the person asking: availability, holds, confirmations, the guest lookup, and the sweep.
A back office that runs the day
A schedule you can work from behind the desk, with bookings you add and cancel yourself, plus a service editor with photo uploads, per-service opening hours over your business defaults, a bookings manager, review moderation, in-app notifications when something arrives or moves, and a dashboard with charts over what actually sells.
A codebase your AI tool can keep extending
Types on everything, folders that follow the features rather than the file extensions, and a comment wherever the reasoning would not be obvious to somebody reading it cold. Availability is where that pays off, because it is the part you are most likely to want changed (a diary per staff member, a class with twelve places), and the version in front of you states plainly which inputs each answer was drawn from.
From founders who build on our templates
Rather than starting from scratch, we built on this foundation using our AI workflow. It allowed us to launch a complete, reliable product for our users with minimal friction.
Jeevan ThomasFounder & CEO, Hado.aiCommon questions
No, and this is the first thing to know before you compare it with anything. The template notifies inside the app (a booking arrives, a booking moves, a review lands) and carries no mail or text provider anywhere in it, so nothing reaches a customer’s inbox or phone. Connecting an email service is a small, well-defined addition, and it is the one to make first: a reminder the day before is the cheapest thing in this whole category, and a confirmation somebody can find in their inbox saves you half your messages.
Not as it stands. A cart and a guest checkout form exist, but no payment provider is wired in. A booking is recorded, not charged. Plenty of service businesses run exactly that way and take payment on the day. If you want a deposit, it is a real addition rather than a switch: a provider, a refund path, and a decision about what a late cancellation means once money has changed hands. Worth noting that the cancellation policy the settings ship is a sentence you show, not a fee anything collects.
Two things, and both matter. Availability is worked out on the server from your opening hours, the length of the service, and every booking already confirmed or pending, so a taken slot is never offered. Then, when somebody starts filling in their details, the slot is held for a set number of minutes (fifteen by default), which takes it out of circulation while they finish. If they abandon it, a scheduled job releases the hold. The check worth asking your AI tool to demonstrate is the race: two bookings for the same slot at the same moment, and one of them refused.
Yes, and it is one of the more carefully built parts of the template. A guest books with a name and an email address. A database trigger generates a long random token with an expiry, and finding that booking again means presenting the token, the email address, and the booking together. Every attempt is logged and lookups are rate-limited. The rate limiting lives in the function’s own memory, so it resets when a new instance starts. If your page gets real traffic, that is the piece to move somewhere shared.
No, and this is the half an AI coding tool handles best. Describe the behaviour you want in ordinary language and back come the tables, the accounts, the rule deciding who may read which appointments, the function turning opening hours into free slots, the hold keeping a slot out of circulation, and the scheduled job that clears up after whoever did not finish. Your share of it is opening a free Supabase project for all of that to be written into.
The template ships one calendar, with each service carrying its own opening hours over your business defaults, which covers a solo practitioner and a small shop where anybody can do anything. A diary per staff member, or a room that can only host one appointment at a time, is a described change to the availability function rather than a rebuild, and it is worth deciding before you load your real services: whether a slot belongs to the business or to a particular person is the one structural question this application asks.
Yes. A cart exists, and a booking carries a list of services with the durations chosen, so a cut and a colour become one appointment long enough for both rather than two bookings that happen to be adjacent. That is a genuinely useful thing to have already built, because the arithmetic (one slot long enough for the total, checked against your opening hours) is the part people get wrong when they add it later.
Every appointment is stored as an absolute moment, and the screens draw it in the viewer’s local time, which is the right shape. What still needs your attention is testing it: open your own booking page with your computer set to another time zone and check that the slot you picked is the slot that shows up, and do the same around a clock change if you work early or late. Almost every booking bug that reaches production is a time bug, and none of them are hard once you have looked.
Only somebody with a finished appointment of their own, and that rule lives in the database rather than in the form. The insert is checked against a booking belonging to that customer, so no screen can bypass it. You moderate what appears from the admin panel. The reason to care about where the rule lives is that a service page carrying ratings is worth something to strangers who want to post on it.
Two lines: somewhere to keep the data, somewhere to serve the app, and each has a free tier. Once customers are booking for real, the one upgrade worth making is Supabase Pro, from $25/mo, less for the storage than for the pause, since a free project sleeps after a quiet week and a sleeping booking page is a business that looks shut. Past those two lines, nothing on the bill moves when you take on staff or get busier. Add an AI feature and you pay whichever model you call for the text you send it.
Nothing here is proprietary. Your services, bookings, customers, and reviews sit in plain PostgreSQL tables that any Postgres host will accept from a standard dump, and the service photographs are files you can copy like any others. That is worth more than it sounds in this category: your calendar is the record of your business, and it is exactly the thing that makes a platform expensive to leave.
Yes, and it is worth doing before you print the address anywhere. Every host listed above attaches a domain in a few clicks with HTTPS included, and a booking page on your own domain is the difference between sending customers to you and sending them to a page with somebody else’s name above yours. Do it before the links go into your reply templates, your invoices, and the sign in your window.
No. You describe the screen you want in the chat and v0 generates the React and Next.js code behind it. Sign up, pick a plan, and connect a database once your screens need real data. The setup section above walks through each one.
Both, but not automatically at the same time. v0 generates the interface first. The backend (a database and the API routes that use it) is something you ask for once a screen actually needs to save or load real data.
Free plan credits reset monthly, and the 7-messages-a-day cap resets every day. If you hit either limit mid-build, what you’ve already built stays put. You wait for the reset or move up to Plus to keep going right away.
References
Sources checked August 2026- 01No-show rates before and after a scheduling intervention (JMIR Formative Research, January 2025). pmc.ncbi.nlm.nih.gov (study published January 2025)
- 02No-show fees in medical practices (MGMA Stat poll, 622 responses, January 2025). mgma.com (published January 2025)
- 03Patient digital self-scheduling adoption (MGMA Stat poll, 244 responses, July 2025). mgma.com (published July 2025)
- 04Pricing (per-seat tiers, free-tier event-type limit), Calendly. calendly.com
- 05Pricing (per-calendar tiers, both billing bases, trial), Acuity Scheduling. acuityscheduling.com
- 06Features (reminders, calendar sync, deposits at booking), Acuity Scheduling. acuityscheduling.com
- 07Pricing (monthly booking caps, custom-feature caps), SimplyBook.me. simplybook.me
- 08Pricing (free tier appointment cap, Pro per user), Setmore. setmore.com
- 09Pricing (Pro plan, free-tier limits, project pausing), Supabase. supabase.com
- 10Web developer hourly rates 2026 (freelance and agency benchmarks). developex.com
- 11Row Level Security, Supabase docs. supabase.com
- 12Storage access control, Supabase docs. supabase.com
- 13v0 homepage. v0.app
- 14Pricing (Plus, Business), v0. v0.app
- 15Pricing details (Free tier, credit rollover), v0 docs. v0.app
- 16Databases (connecting Supabase and others), v0 docs. v0.app
- 17Full-stack apps, v0 docs. v0.app
- 18GitHub integration, v0 docs. v0.app
- 19Deployments, v0 docs. v0.app
- 20Design mode, v0 docs. v0.app
- 21Versions, v0 docs. v0.app
- 22Pricing (Hobby plan), Vercel. vercel.com
This guide is general information, not legal advice. What you may keep about a customer, how long you may keep a record of where they were, and whether you may charge for a missed appointment vary by country and by state, so check your own rules before you collect anything or write a fee into a policy. Third-party prices, plan terms, and market rates are quoted from the sources above and were last checked on the date shown. Vendors change them without notice, so confirm before you budget. Build hours and the cost estimates derived from them are our own estimates, not quotes. v0 is a product of Vercel. Verify current capabilities and pricing before relying on them.