Skip to content
ChatfuelSDK
Modules

Bookings

A booking workspace over bookingsV2 — the six sections, where availability comes from, and the time-zone rule the schema does not carry.

bookingsV2(startTime, endTime) takes a window and nothing else — no cursor, no filter, no sort, no search, no count. Every section of this module is built on that: it asks for exactly the days it draws, filters and sorts in the browser, and says how much it is holding rather than pretending to page.

Six sections over one booking list: a calendar you drag appointments around, an appointments table, staff with weekly hours and Google Calendar sync, the services catalog, the AI booking settings and the bot time zone, and insights over the loaded window.

Installed as

Wizard idbookings
Flag--modules bookings
SelectionDefault — --yes installs it
Requires
Recommendsknowledge-base, contacts
Skillchatfuel-bookings

Routes and views

The section is a path segment; everything else is a query parameter. calendar is DEFAULT_VIEW, so it has no segment of its own.

PathWhat it shows
/bookingsCalendar — day, week or month, columns by day or by specialist, drag to move, drag an edge to resize, drag empty grid to create.
/bookings/appointmentsUpcoming, past or a custom range as a table, with bulk status changes and CSV export.
/bookings/staffSpecialists: weekly working hours, the services they offer, and their Google Calendar connection.
/bookings/servicesThe catalog — duration, price, availability, images.
/bookings/settingsNotification channel, confirmation, the 24 h / 2 h notices, language, the bot time zone. AI autonomy is read here, not set.
/bookings/insightsStatus mix, no-show and cancel rates, attended revenue per currency, utilisation per specialist, busiest weekdays and hours.

The parameters the module owns:

ParameterValue
bThe open booking's id. A drawer below 1280px, an inline second column above it.
specialist, service, statusComma lists. Shared by the calendar, the appointments list and insights.
modeday, week (the default) or month.
dateAnchor day, YYYY-MM-DD. Absent means today.
bytime (the default) or specialist — what a column is.
colorspecialist (the default) or status.
rangeupcoming (the default), past or custom.
from, toThe custom range, and the custom insights period.
qSearch over the loaded rows.
sort<column>:asc or :desc.
periodweek, month, 30d (the default), 90d or custom.
sA specialist id, or new, on the Staff section.
densitycomfortable (the default) or compact.
new1 opens the booking wizard, prefilled by start, end, contact, ns (specialist) and nsvc (service).

?new=1&contact=<id> is the deep link a "Book" button in Live Chat uses: the wizard opens on the customer step with that contact chosen. A ?week= from an older link is still read, as mode=week&date=, and never written back. Defaults are omitted from what is written, and an unrecognised value anywhere renders the default rather than throwing.

The model

An appointment is a BookingBase in bot.bookingsV2, and queries and mutations answer with that interface while subscriptions answer with the concrete type — so a selection always goes through ... on Booking / ... on BookingWithGoogleCalendarRef. Deleted services and specialists still arrive, as DeletedGoodsService and DeletedSpecialist, keeping enough of themselves to render a past appointment.

Six statuses, in the order the whole module uses: Pending, Confirmed, Attended, No-show, Reschedule, Canceled. Everything but Canceled occupies its slot. Only five of them are targets. A booking is born Pending and can never return to it: → Pending answers InternalServerError from every state, including Pending → Pending, so the app never offers it.

Availability is a separate call, not a field on the calendar. GoodsService.bookingAvailableStartTime(botID, date) answers one service × one day, per specialist, with start-time ranges whose end is inclusive — a 30-minute service on a 09:00–18:00 day answers 09:00–17:30. A start s is bookable when start ≤ s ≤ end; slicing it with the usual s + duration ≤ end silently drops the last slot of every period. Existing bookings are already subtracted, Canceled and deleted ones free the slot, and the module offers a start every 15 minutes inside what comes back.

Which leaves the fact that decides every timestamp you send:

A Time with a zero offset is read as the bot's wall clock, not as UTC. 2026-08-21T13:00:00Z was stored and echoed as 13:00:00-06:00 on a bot in America/Mexico_City. A non-zero offset is honoured as an instant. So toISOString() puts a booking at the wrong hour and nothing errors — the record round-trips, and only a comparison against the specialist's schedule shows it. Format every instant with the bot zone's real offset (lib/zone.ts, toZoneIso), range bounds included. Schedules and availability are HH:mm in that same zone, which is why slots stay in the bot's zone even when you view in your own.

Specialists are bot.specialists, unpaginated. A schedule is seven named days with one optional break each, HH:mm in the bot zone, and availability is computed from that schedule — not from the knowledge base's business hours, which are a different, unrelated shape. specialistCreate and specialistUpdate replace profile, schedule and services together.

Configuration

VariableNotes
CHATFUEL_TOKENSecret. Server-side only.
VITE_CHATFUEL_WORKSPACE_IDThe wizard's workspace step writes it.
CHATFUEL_API_BASEDefaults to https://panel.chatfuel.com.

Nothing here is specific to this module. Environment variables has what each one is and which side reads it.

Permissions

ObjectActionRequired for
PeopleViewReading the calendar and appointments
PeopleEditCreating, moving, resolving and deleting bookings
AiEditManaging staff and services, the AI booking settings and the bot time zone

Limits

There is no server-side filter, search, sort, count or pagination over bookings. Filters, search and sort all run over the loaded window, which is why the header says 12 of 40 and the search box says "Search loaded rows…". To see more, the list asks for a bigger window: Upcoming grows 90 days forward, Past 30 days backward, and every window is capped at 366 days.

There is no aggregation in the booking API. Every insights figure is a fold over the loaded rows and every card prints its coverage. Four things are absent from the data rather than unbuilt: lead time (a booking carries no createdAt), trends across more than one window, anything server-aggregated, and revenue as one number across currencies.

bookingUpdateV2 is a full replace, and there is no patch. There is no status on the inputs and no dedicated cancel or reschedule mutation either — Canceled and Reschedule are status values, moved through bookingStatusResolveV2, and a reschedule is an update of the times. Delete has no counterpart: there is no restore mutation, so delete asks first and undo does not cover it.

The API validates nothing on the calendar. It accepted overlapping bookings for one specialist, bookings outside working hours, bookings in the past, a 24-hour booking, and a booking with neither service nor specialist. Only the wizard, going through availability, is opinionated — it warns, and the panel does not stop you.

Availability needs a service, and answers one day at a time. There is no availability without a service and none for a range of days. A specialist with no working hours answers hasSchedule: false, always empty — their existing bookings still show on the calendar and an operator can still book them by hand. A schedule allows exactly one break per day, because the input has one.

Only WhatsApp contacts can be booked — anything else is BookingContactPlatformNotAllowed. On a WhatsApp-connected bot the API turns inline-contact input into a real WhatsappContact, so the same phone booked twice yields one real contact rather than an inline one.

Two things this module cannot finish. AI autonomy is read on the Settings page and changed in the automations module — the legacy setter is deprecated and answers BotMigratedToNewFuelySettings on a migrated bot. And Google's sign-in happens on Chatfuel's own page: this app creates the connection link, shows who created it, starts the sync and follows the task, but the OAuth round trip needs credentials it does not have.

The only reminders are bot-wide. The 2-hour and 24-hour appointment notices in Settings are the whole set — there is no per-booking reminder and no recurring booking in the API. calendarLandingURL, the hosted booking page, is read-only and an experiment: null outside it.

The module's own ceilings:

Offered start times, every15 minutes
Calendar snap, and a keyboard nudge15 minutes
Shortest booking a drag can make15 minutes
Booking duration in the panel5 minutes to 24 hours
Upcoming window grows by90 days
Past window grows by30 days
Any window366 days
Undo window60 seconds, one entry, never into Pending
Default service duration30 minutes
Service title / description120 / 1000 characters
Images on a service10
Specialist first and last name60 characters each
About a specialist1000 characters

On this page