Booking widget
The widget can be integrated in two ways:
- Script tag — a single HTML snippet that renders the booking form. No JavaScript is required. This is the snippet provided in the restaurant’s back office.
- JavaScript API — the same widget, created and controlled from your own code. Use this when the page needs to set context, respond to events, or manage the widget’s lifecycle.
Script tag
Section titled “Script tag”Add the snippet where the booking form should appear:
<div id="service-widget"></div><script src="https://app.useservice.app/widget/widget.js" data-slug="chez-marie" data-mode="inline" async></script>data-slug identifies the restaurant. The back office shows the snippet with
the correct slug under Settings → Widget.
The widget loads the restaurant’s availability, takes the booking, and confirms it. No further integration is required.
Attributes
Section titled “Attributes”| Attribute | Values | Description |
|---|---|---|
data-slug | Restaurant slug | Required. Identifies the restaurant. |
data-mode | inline, sticky, popover | Presentation mode. Defaults to inline. |
data-position | bottom-right, top-left, and so on | Anchor for the floating trigger. sticky only. |
Presentation modes:
| Mode | Behaviour |
|---|---|
inline | Renders in the flow of the page, inside #service-widget. |
sticky | Renders a floating Reserve button that opens the form. |
popover | Renders nothing until opened, either by a trigger element or by the JavaScript API. |
data-mode="manual" was the earlier name for popover. It was removed on
6 August 2026: it now falls back to inline like any other unrecognised value.
The back office emits popover, which is also the name the JavaScript API uses.
Trigger elements
Section titled “Trigger elements”In popover mode, any element with the data-service-widget-open attribute
opens the widget:
<button data-service-widget-open>Book a table</button>The element is styled by the host page. No JavaScript is required.
Loading the bundle
Section titled “Loading the bundle”https://app.useservice.app/widget/widget.js is a stable address that
redirects to a content-hashed file. The hashed file is cached for a year and
never changes; the redirect is cached for five minutes, which is how long a
release takes to reach pages that already have the widget on them.
Load it from that address. A copy served from your own domain, a copy compiled into your bundle, and a CMS cache plugin that rewrites the URL all pin the version you took and stop receiving fixes.
When the script runs
Section titled “When the script runs”async is supported and is what the back-office snippet emits.
The data-* attributes are read from the script tag that loaded the bundle, so
they belong on that tag and not on another one.
The script installs window.ServiceWidget while it executes, and mounts the
script tag’s own widget once the document has been parsed. With async, the
point at which it executes is not ordered against your inline scripts, so code
calling ServiceWidget.create() runs from the load event — as in the example
below — or from the script tag’s own onload.
ServiceWidget.version reports the bundle version. One version is published at
a time; there is no version to pin and no upgrade to schedule.
JavaScript API
Section titled “JavaScript API”The script tag installs window.ServiceWidget. ServiceWidget.create() returns
an instance:
<script src="https://app.useservice.app/widget/widget.js" async></script><script> window.addEventListener("load", async () => { const widget = ServiceWidget.create({ slug: "chez-marie", mode: "popover" }); await widget.ready;
widget.on("reservation:confirmed", ({ reservation }) => { console.log("booked", reservation.publicId); });
document.querySelector("#book").addEventListener("click", () => widget.open()); });</script>create() options
Section titled “create() options”| Option | Type | Description |
|---|---|---|
slug | string | Required. Identifies the restaurant. |
mode | inline | popover | sticky | Presentation mode. Defaults to inline. |
container | string | HTMLElement | Mount point for inline. Defaults to #service-widget. |
locale | string | Initial guest locale. Invalid values fall back to fr. |
position | string | Anchor for the floating trigger. sticky only. |
Instances
Section titled “Instances”Each call to create() returns an independent instance. open(), close(),
setContext(), setGuestToken() and on() are methods on that instance, not
on ServiceWidget.
A page may carry several instances — a group site listing several restaurants — and the global object cannot address one of them individually.
Behaviour to account for
Section titled “Behaviour to account for”widget.ready resolves when the instance has mounted. Methods called before
that point are honoured rather than dropped, so awaiting it is optional. It also
resolves if the instance is destroyed before mounting, so it never hangs.
create() throws synchronously when mode: "inline" and its container cannot
be resolved. An incorrect selector surfaces at the call site rather than as a
form that does not appear.
on() returns an unsubscribe function. Call it when the subscribing component
is removed.
Events
Section titled “Events”widget.on("reservation:created", ({ reservation }) => {});widget.on("reservation:confirmed", ({ reservation }) => {});reservation:created fires for every booking that reaches an outcome,
including bookings awaiting restaurant approval and bookings holding a table
pending a card guarantee. reservation:confirmed fires only when the guest
holds a confirmed table.
Sending a confirmation message on reservation:created will eventually notify a
guest who has an unpaid hold rather than a table.
The event payload contains publicId and no contact details. publicId is the
join key for the API and for webhooks, both
of which run server-side.
The full catalog is in Events.
Where bookings are stored
Section titled “Where bookings are stored”The widget writes to the restaurant’s calendar directly. There is no step in which the host receives a booking and forwards it, and no API key is involved: checking availability and booking are public operations against a single restaurant.
To receive bookings in other systems, read them through the API or subscribe to webhooks. This is the same path a telephone booking takes.