# Slot booking

> Let customers book a time slot from a calendar you design in Webflow — an appointment without payment, a paid appointment, or a pickup or delivery window in the cart — with the schedule and the bookings managed in the Shopify admin.

Status: Ready (Smootify 2.0)  
Plan: Server plan  
Add-on: Booking  
URL: https://www.smootify.io/docs/features/booking  
Updated: 2026-10-13

With Booking, customers pick a time slot from a calendar you design in Webflow. The same calendar works three ways: an appointment booked with a form and no payment, an appointment paid with a product, or a pickup or delivery window chosen in the cart. You set the opening hours and the places of each slot on the product, in the Shopify admin, and you see and manage the bookings there. Smootify counts the places, so two customers never get the last one.

> [!NOTE]
> Booking needs the **server** plan and **Booking** turned on in the Smootify dashboard. It runs on the site's licensed domain. On your `.webflow.io` address it runs once the site is paid, not during a trial.

## 1. Set the schedule in Shopify

Every bookable thing is a product: a haircut, a consultation, a pickup at your shop. Open the product in the Shopify admin. The **Smootify booking** block on its page shows the next bookings; on a product that is not bookable yet it says so, with a **Set up booking** button. **Edit schedule** opens the schedule, the **Booking schedule** action, which is also in the product’s **More actions** menu:

- **Mode:** appointment, pickup or delivery. It also names the slot on the order: “Appointment”, “Pickup” or “Delivery”.
- **Weekly hours**, and the **closed days** or days with **special hours**, such as a holiday.
- **Slot length** and **places per slot**: how long a slot lasts and how many customers it takes.
- **Notice:** how long before a slot it can still be booked. **Days ahead:** how far in the future customers can book.
- **Hold time**, from 5 to 120 minutes: how long a slot stays held for a customer, after a paid appointment is added to the cart or while a pickup or delivery window goes through checkout.

From the same block you cancel a booking with **Cancel** next to it. **Book by hand** opens the **Book a slot by hand** action, also under **More actions**: it books a slot for a phone call or a customer at the counter, even beyond the places, with a warning. A paid booking that found its slot already full shows as a conflict in the block. Every booking is an entry of the **Booking** metaobject, under **Content › Metaobjects**.

Then turn on the checkout rule: in **Settings › Checkout › Checkout rules**, add **Smootify booking**. It stops the checkout of a bookable product that has no slot, or a slot already past. It needs no new permission.

## 2. Design the calendar

`booking-calendar` shows the slots of one product for a number of days. Add a Custom element with the tag `booking-calendar`. Outside a product, give it the custom attribute `data-product-id` with the Shopify ID of the bookable product; inside a `smootify-product` it takes that product. `days` sets how many days it shows at once (7 by default).

What Smootify needs inside it:

- a **day** template, with the custom attribute `booking="day"`: Smootify repeats it for each day of the range;
- inside the day, a **slot** template with `booking="slot"`, repeated for each slot of the day. Make it a Custom element with the tag `button`: a full slot is then disabled for the keyboard too.

Everything else is up to the design:

- **On the day:** `booking="weekday"` (Mon), `booking="day-number"` (12), `booking="month"` (October) or `booking="date"` (Monday 12 October), in the site’s language.
- **On the slot:** `booking="time"`, its time in the time zone of the store’s location; `booking="left"`, the places left; `booking="capacity"`, its places.
- **Anywhere in the calendar:** `booking="range"` (the days shown), `booking="selection"` (the slot picked), `booking="error"` (why a slot could not be held, such as a window someone else took before checkout), and two Custom elements with the tag `button` and `data-action="prev"` or `"next"` to move by a range of days.

```html
<booking-calendar data-product-id="{Shopify ID}" days="7">
  <button data-action="prev">Previous</button>
  <div booking="range">12 – 18 October</div>
  <button data-action="next">Next</button>
  <div booking="day">
    <div booking="weekday">Mon</div>
    <div booking="day-number">12</div>
    <button booking="slot"><span booking="time">10:00</span> <span booking="left">3</span> left</button>
  </div>
</booking-calendar>
```

**Style the states with combo classes.** On a day: `is-today`, `is-closed` (no slot that day) and `is-full` (every slot taken). On a slot: `is-selected` when picked and `is-full` when it has no place left; a full slot is also disabled. A click picks a slot, a second click lets it go. `prev` is disabled, with `is-disabled`, on the range that starts today. On the calendar: `is-loading`, `is-error`, `has-selection`, and `is-not-bookable` on a product without a booking schedule.

**One product template for bookable and other products.** Put the calendar in a block shown only on bookable products, such as one with `tagged="bookable"`. While the block is hidden, the calendar asks for nothing.

**A service with its own page.** A visit or a consultation is often kept out of the shop: hidden from search with `seo.hidden` and left out of the CMS sync, so it has no product page. Book it from a static page, such as `/book-a-visit`: put a `smootify-product` with `data-id` set to the service’s Shopify ID, and the calendar, the price and Add to cart inside it. Link the page from the navbar or the footer. Every link to `/products/<handle>` of the hidden product goes to a 404.

The availability is kept for a minute, so moving between weeks or opening the page again asks Shopify nothing. A customer’s own booking updates the places at once.

## 3. Choose where the slot goes

The calendar books the slot according to what is around it.

### An appointment without payment

Put the calendar and a Form Block inside a Custom element with the tag `booking-form`. The form books the slot picked; without one it answers “Choose a slot”. The fields whose **Name** is `name`, `email`, `phone` or `note`, in any case (Webflow’s own Name and Email fields work), are saved with the booking; other fields are not sent, and a field is required when you make it Required in Webflow. When the customer is logged in, the booking is linked to them as well. Webflow’s Success and Error messages show the answer. When the slot fills up in the meantime, the Error message shows and the calendar shows its places again.

```html
<booking-form>
  <booking-calendar data-product-id="{Shopify ID}">
    <div booking="day">
      <div booking="date">Monday 12 October</div>
      <button booking="slot"><span booking="time">10:00</span></button>
    </div>
  </booking-calendar>
  <div class="w-form">
    <form>
      <input name="name">
      <input name="email" type="email">
      <input type="submit" value="Book">
    </form>
    <div class="w-form-done"><div>Booked: see you then.</div></div>
    <div class="w-form-fail"><div>That slot is no longer free.</div></div>
  </div>
</booking-form>
```

### A paid appointment

Put the calendar inside the product’s add to cart, in its form. When the customer adds the product, the slot is held for the quantity and written on the product’s lines, named “Appointment” or as the `property` attribute of the calendar says. Without a slot, or with a full one, the product is not added and the calendar shows why. If Shopify refuses the addition, or the network fails, the slot is given back right away, so it does not stay held for nobody.

```html
<smootify-add-to-cart>
  <div class="w-form">
    <form>
      <booking-calendar>
        <div booking="day">
          <div booking="date">Monday 12 October</div>
          <button booking="slot"><span booking="time">10:00</span></button>
        </div>
        <div booking="selection"></div>
      </booking-calendar>
      <input type="submit" value="Book and pay">
    </form>
  </div>
</smootify-add-to-cart>
```

### A pickup or delivery window

Put the calendar inside `smootify-cart`, with `data-product-id` set to the product that holds your pickup or delivery schedule. Picking a slot writes it on the cart, as “Pickup” or “Delivery” with the day and time, next to the cart attributes already there. It holds no place yet, so a customer who tries a few windows blocks none of them. At checkout Smootify holds the picked window for the payment. If someone else took it in the meantime, the checkout stops: the calendar shows the reason in `booking="error"`, shows its free places again and comes into view, and the cart form says the slot is no longer available.

**Make the window required** with the custom attribute `data-required` on the calendar, as a delivery usually needs: the checkout stops until the cart has a window of that product. A picked window is enough, and the calendar comes into view with its first free slot when it is missing. The message shows in `booking="error"` and in the cart form’s error, and the calendar gets `is-error`. It is “Choose a slot” unless you write your own in `data-error-required`, such as “Choose a delivery window”. Without `data-required`, or with `data-required="false"` as a Webflow boolean property turned off writes it, the customer can check out with no window.

```html
<booking-calendar data-product-id="{Shopify ID}" days="5" data-required data-error-required="Choose a delivery window">
  <div booking="day">
    <div booking="weekday">Mon</div>
    <button booking="slot"><span booking="time">17:00</span></button>
  </div>
  <div booking="error"></div>
</booking-calendar>
```

## 4. Confirm the bookings with Shopify Flow

A paid slot becomes a booking when the order is placed. Smootify’s app gives Shopify Flow two templates; install both from Flow’s templates, on any Shopify plan, Basic included:

- [**Confirm the slots booked with Smootify**](https://www.smootify.io/flows/confirm-booking), on Order created;
- [**Give back the slots of a cancelled order**](https://www.smootify.io/flows/release-booking), on Order cancelled.

When a slow payment finds its slot already full, Flow’s trigger **Smootify booking conflict** starts: use it to tell your staff, so they can move the booking or refund the order.

Every attribute and element is in the Reference: [`booking-calendar`](https://www.smootify.io/docs/reference/elements/booking-calendar) and [`booking-form`](https://www.smootify.io/docs/reference/elements/booking-form).

## Before you publish

Book a slot on your published site in each place you use the calendar, then check the booking on the product’s Smootify booking block.

> [!STUCK]
> - **The calendar shows no days and has the class `is-not-bookable`.** The product has no booking schedule. Set one in the Booking app, or hide the calendar on that product.
> - **The calendar disappears.** It has no `booking="day"` template, or the site's licence has no Booking. Add the template, and turn on Booking in the Smootify dashboard.
> - **The checkout lets a booked product through without a slot.** The checkout rule is off. Add Smootify booking in Settings › Checkout › Checkout rules.
> - **Customers check out without a pickup or delivery window.** The cart's calendar has no `data-required`. Add it.
> - **Orders do not confirm their slots.** The Flow template Confirm the slots booked with Smootify is not installed or not turned on.
