Developers

Take bookings on your website

Let customers pick a free time on your own website. Every booking lands in Frictionless: on the Bookings page, in your inbox as a conversation you can reply to, and in your team's email.

Last updated 7 October 2026

Before you start

  1. Open Integrations → Calendar in Frictionless, set your opening hours and press Turn on calendar. Bookings are on the Growth plan and above.
  2. 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.
  3. Your workspace ID is filled into the code under Put it on your website on that page.

Four ways to take bookings

WayCodeBest for
Booking linkNoneWhatsApp, your bio, emails, a button on any site
Our formTwo linesAny website, including WordPress and site builders
Your own formOne attribute, and named boxesSites with their own design and a developer
Book tab in the chatA switch, or one lineSites already running the Frictionless chat

Whichever you use, people pick from your real free times, so two people can never book the same time.

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:

SettingWhat it changes
--fric-accentThe Book button and the picked day and time. Text on it turns white or dark, whichever reads better.
--fric-radiusCorner rounding of boxes and buttons. Default 12px.
--fric-fieldBackground of boxes and time buttons. Default white.
--fric-lineBorder colour.
--fric-text, --fric-mutedMain 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

NameWhat goes in it
nameTheir name. Always needed.
phoneA number your team can call or WhatsApp. Always needed. Local numbers like 0803 123 4567 are fine.
emailOptional, 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 nameShown on the Calendar page, like department or reason_for_visit. Renaming a question keeps its name, so your form keeps working.
startA <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: Yes or No, from a dropdown, radio buttons or a checkbox with value="Yes".
  • Date: YYYY-MM-DD, which is what <input type="date"> sends.
  • Boxes with other names are ignored.

When they press the button

  • Your required attributes 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-success is shown, with data-frictionless-when filled 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.