Before you start
- Open Integrations → Calendar in Frictionless, set your opening hours and press Turn on calendar. Bookings are on the Growth plan and above.
- On the same page, set up the Booking form: whether to ask for an email, the note box, and up to eight questions of your own, such as Department or Service. The same form is used everywhere people book, so you set it once.
- Your workspace ID is filled into the code under Put it on your website on that page.
Four ways to take bookings
| Way | Code | Best for |
|---|---|---|
| Booking link | None | WhatsApp, your bio, emails, a button on any site |
| Our form | Two lines | Any website, including WordPress and site builders |
| Your own form | One attribute, and named boxes | Sites with their own design and a developer |
| Book tab in the chat | A switch, or one line | Sites already running the Frictionless chat |
Whichever you use, people pick from your real free times, so two people can never book the same time.
Booking link
Every workspace has one, at usefrictionless.com/c/your-workspace/book. Copy it from Integrations → Calendar → Booking link, or from the Bookings page. It opens straight on your free times, with your name and picture at the top, and works as soon as the calendar is on.
Our form
Paste this where the form should appear. It draws your free times and your booking form right there.
<div data-frictionless-booking="YOUR_WORKSPACE_ID"></div>
<script async src="https://usefrictionless.com/widget/book.js"></script>It uses your site’s font and your chat colour. It lives in its own Shadow DOM, so your CSS can’t break it and its CSS can’t leak onto your page. Match it to your design with these on the div:
| Setting | What it changes |
|---|---|
--fric-accent | The Book button and the picked day and time. Text on it turns white or dark, whichever reads better. |
--fric-radius | Corner rounding of boxes and buttons. Default 12px. |
--fric-field | Background of boxes and time buttons. Default white. |
--fric-line | Border colour. |
--fric-text, --fric-muted | Main and secondary text colour. |
data-heading | "off" drops our “Book an appointment” heading, when your page has its own. |
<div
data-frictionless-booking="YOUR_WORKSPACE_ID"
data-heading="off"
style="--fric-accent:#8A1420; --fric-radius:16px; --fric-field:#FBF5F4; --fric-line:#F0DADA"
></div>Your own form
Keep your form exactly as designed. Add data-frictionless-booking to the <form>, give each box the right name, and load the script. Leave the times box empty: the script fills it with your free times, grouped by day, and keeps your first option as the placeholder.
<form data-frictionless-booking="YOUR_WORKSPACE_ID">
<input name="name" placeholder="Full name" required>
<input name="phone" type="tel" placeholder="Phone number" required>
<select name="department" required>
<option value="">Select department</option>
<option>Cardiology</option>
<option>Paediatrics</option>
</select>
<select name="start" required>
<option value="">Preferred date</option>
</select>
<button>Request appointment</button>
<div data-frictionless-success hidden>
Thank you. We'll see you on <span data-frictionless-when></span>.
</div>
</form>
<script async src="https://usefrictionless.com/widget/book.js"></script>The Calendar page writes this form for you from your booking form, with every box name and dropdown choice filled in: Put it on your website → Your own form.
Box names
| Name | What goes in it |
|---|---|
name | Their name. Always needed. |
phone | A number your team can call or WhatsApp. Always needed. Local numbers like 0803 123 4567 are fine. |
email | Optional, required or ignored, as your booking form says. When given, they get a confirmation with a calendar invite. |
note | “Anything we should know?”, when the note box is on. |
| Each question’s name | Shown on the Calendar page, like department or reason_for_visit. Renaming a question keeps its name, so your form keeps working. |
start | A <select> the script fills with free times. Prefer separate boxes? Use <select name="day"> and <select name="time"> instead, and the times follow the day picked. |
Answers are checked against your booking form:
- Dropdown: the value must be one of your choices. Capital letters don’t matter.
- Yes / No:
YesorNo, from a dropdown, radio buttons or a checkbox withvalue="Yes". - Date:
YYYY-MM-DD, which is what<input type="date">sends. - Boxes with other names are ignored.
When they press the button
- Your
requiredattributes are checked first, by the browser. Then the script sends the form to Frictionless instead of submitting it. If your form already posts somewhere else, remove that, or both will run. - Booked: anything inside the form marked
data-frictionless-successis shown, withdata-frictionless-whenfilled in (for example “Thu 8 Oct at 9:00am”), and everything else in the form is hidden. Without one, the form is replaced by a short “You’re booked” line. - A problem with one box, such as a department left empty, shows on that box as the browser’s own message. Anything else shows in an element marked
data-frictionless-error, or in a line added under the form. - Someone took the time a moment ago: the times are refreshed and they’re asked to pick another.
Events
Our form and your own form fire events on the element with data-frictionless-booking. They bubble, so you can listen on the document:
document.addEventListener("frictionless:booked", (e) => {
// e.detail: { id, title, start, day, time }
// start is an ISO time, like "2026-10-08T08:00:00.000Z"
gtag("event", "booking", { value: 1 });
});
document.addEventListener("frictionless:error", (e) => {
// e.detail: { message, field } field is a box name, or null
});On your own form, call e.preventDefault() in either listener to show the thank-you or the error yourself.
React and Next.js
Load the script once, in a layout that stays mounted. It finds booking forms added later, as visitors move between pages, so render the form wherever you like.
// app/layout.tsx
import Script from "next/script";
<Script src="https://usefrictionless.com/widget/book.js" strategy="afterInteractive" />// app/book/page.tsx
export default function BookPage() {
return <div data-frictionless-booking={process.env.NEXT_PUBLIC_FRICTIONLESS_WORKSPACE_ID} />;
}Your own form works in React too, controlled or not, as long as each box has a name. Render the times box with no options, <select name="start" required />, so React leaves the times the script adds alone.
Book tab in the chat
Already running the website chat? Switch on Book tab in the website chat on the Calendar page, and visitors can book from the chat itself. To open it from a button on your site:
bookButton.onclick = () => Frictionless.open("book");
Frictionless.on("booking", (b) => {
// b: { title, start, day, time }
});open("book") opens the home screen instead while the Book tab is switched off.
Allowed websites
If you’ve listed Allowed websites under Integrations → Website chat, booking only works on those sites, as the chat does. Anywhere else, our form says it isn’t set up for that website.
Content Security Policy
If your site sets a Content Security Policy, allow:
script-src usefrictionless.com
connect-src api.usefrictionless.com
style-src 'unsafe-inline'The chat needs a little more; see its guide. Using both? The chat at usefrictionless.com/widget/v1.js and the booking form can sit on the same page.