JavaScript reference
This page is generated from the widget’s TypeScript declarations, so it describes the contract the bundle actually implements rather than the one it was documented as implementing.
JavaScript API version: 2.8.0 (ServiceWidget.version).
create() options
Section titled “create() options”Passed to ServiceWidget.create(). Everything except slug is optional.
| Name | Summary |
|---|---|
options.slug | Restaurant slug, e.g. "chez-marie". |
options.mode | Presentation mode. |
options.container | Mount point for mode: "inline" — a CSS selector or an element. |
options.locale | Initial guest-facing locale — one of the 11 the widget ships. |
options.position | Anchor for the floating trigger. |
options.analytics | Push booking events onto the page’s window.dataLayer, for Google Tag Manager. |
options.slug
Section titled “options.slug”slug: string;Restaurant slug, e.g. "chez-marie".
options.mode
Section titled “options.mode”mode?: ServiceWidgetMode;Presentation mode. Defaults to "inline".
options.container
Section titled “options.container”container?: string | HTMLElement;Mount point for mode: "inline" — a CSS selector or an element. Defaults
to #service-widget. Ignored by popover / sticky, which mount into a
host of their own at the end of <body>.
create() throws synchronously when an inline container cannot be
resolved, so a typo surfaces at the call site rather than as a silent
no-render.
options.locale
Section titled “options.locale”locale?: string;Initial guest-facing locale — one of the 11 the widget ships. Invalid or
unsupported values fall back to fr. A locale the guest has already
chosen for this restaurant (persisted in localStorage) wins over this.
options.position
Section titled “options.position”position?: PopoverPosition;Anchor for the floating trigger. mode: "sticky" only.
options.analytics
Section titled “options.analytics”analytics?: boolean;Push booking events onto the page’s window.dataLayer, for Google Tag
Manager. Defaults to true.
The widget only pushes an object; it loads no analytics vendor, sets no
cookie and sends no request of its own. Whether anything leaves the browser
is decided by your GTM container and your consent platform. Set false to
push nothing at all.
Instance properties and methods
Section titled “Instance properties and methods”Returned by ServiceWidget.create(). Each instance is independent, and a page may carry several.
| Name | Summary |
|---|---|
widget.slug | The restaurant this instance books for. |
widget.mode | The mode it was created with. |
widget.ready | Resolves once the instance has mounted. |
widget.open() | Show the widget. |
widget.close() | Hide the widget. |
widget.isOpen() | Whether the widget is currently visible. |
widget.setContext() | Pre-position the funnel — party size, date, time, seating, service, locale, guest request. |
widget.setGuestToken() | Hand over a signed guest token, so the funnel opens pre-identified. |
widget.setLocks() | Grey out fields the guest may not change in the widget. |
widget.on() | Subscribe to an event. |
widget.destroy() | Unmount this instance and remove every DOM node it owns — including its own portal host. |
widget.slug
Section titled “widget.slug”readonly slug: string;The restaurant this instance books for.
widget.mode
Section titled “widget.mode”readonly mode: ServiceWidgetMode;The mode it was created with.
widget.ready
Section titled “widget.ready”readonly ready: Promise<void>;Resolves once the instance has mounted. Also resolves if the instance is
destroyed before mounting, so await widget.ready can never hang.
widget.open()
Section titled “widget.open()”open(): void;Show the widget. Safe to call before ready — the open state lives
outside React, so an early call is honoured by the first render rather
than dropped. No-op in inline mode, which is always visible.
widget.close()
Section titled “widget.close()”close(): void;Hide the widget. The React tree is kept alive (display:none + inert),
so anything the guest already typed survives a close/reopen. No-op in
inline mode.
widget.isOpen()
Section titled “widget.isOpen()”isOpen(): boolean;Whether the widget is currently visible. Always true for a mounted inline instance.
widget.setContext()
Section titled “widget.setContext()”setContext(hints: WidgetContextHints): void;Pre-position the funnel — party size, date, time, seating, service, locale, guest request.
The most specific and most recent of the three transports, so it wins
over the script tag’s data-* and over URL parameters. That
ordering exists because context changes after load: the guest lands on the
page, picks dates in the host’s own search box, and only then clicks
Book. Merging is per field, so passing { partySize } leaves a date set
earlier alone.
Safe to call before ready — the value is held outside React, so an early
call is simply what the first render reads.
These are hints, not identity: forgeable by anyone with devtools, and harmless because the guest can change all of it anyway. Nothing here authenticates a guest, and no contact detail belongs in it.
widget.setGuestToken()
Section titled “widget.setGuestToken()”setGuestToken(token: string | null): void;Hand over a signed guest token, so the funnel opens pre-identified.
The other tier. setContext carries hints — forgeable, harmless,
treated exactly as if the guest had typed them. This carries an
assertion: your backend HMACs the guest’s contact details with your
integration secret, and we skip the details step, attach to the right
profile and honour the consent you already collected. Without a signature
any page — or anyone with devtools — could assert any identity and read
back a profile, so identity is signed or it is not asserted.
widget.setGuestToken(await myBackend.mintServiceToken(currentUser));- Mint per widget-open, not per session. The token lives 15 minutes: long enough to book, short enough that a leaked one is worthless. A session-scoped token would also be minted before your own flow knows what it is booking.
- The guest can still edit anything it asserts. A prefilled field is a prefilled field. Edit one and it simply stops being asserted, for that field only.
- Expiry is visible, never silently re-minted — the guest is told the session expired and continues anonymously.
- Pass
nullto drop it, e.g. when your own user signs out.
Safe to call before ready, like setContext. Nothing here is trusted
client-side: the token is verified server-side on every request that
carries it, and this widget only ever renders what the server says it
claims.
widget.setLocks()
Section titled “widget.setLocks()”setLocks(locks: { fields: readonly ServiceWidgetLockableField[]; reason?: string }): void;Grey out fields the guest may not change in the widget.
⚠️ Presentation only — the server does not enforce these. A booking that contradicts them is accepted. They travel on your page, so anything can change them, and the create endpoint cannot tell an unsigned lock from no lock at all.
Use it when the value came from your own page and you simply want the funnel to reflect it — the guest picked a date in your search box, and re-picking it inside the widget would be a second, confusing chance to disagree with themselves.
When the value must actually hold — a voucher valid on one date only, a
prepaid slot — put the field in locked inside the signed token instead.
That tier the server enforces: it refuses a create that contradicts it.
The two can be combined; the signed set always wins.
reason is shown beside each locked field. Supply the why
(« Bon cadeau BC-4471 »), not a whole sentence — the widget adds its own
localized ”· date fixée” per field.
widget.on()
Section titled “widget.on()”on<K extends ServiceWidgetEventName>(event: K, handler: (payload: ServiceWidgetEventMap[K]) => void): ServiceWidgetUnsubscribe;Subscribe to an event. Returns the unsubscribe function.
widget.destroy()
Section titled “widget.destroy()”destroy(): void;Unmount this instance and remove every DOM node it owns — including its own portal host. Other instances on the page are untouched. Idempotent.
Events
Section titled “Events”Subscribe with widget.on(name, handler).
| Name | Summary |
|---|---|
ready | The instance has mounted; its methods are live. |
opened | The popover became visible. |
closed | The popover was hidden. |
step:changed | The guest moved to another step of the funnel. |
reservation:created | A reservation now exists server-side — including non-final outcomes (pending approval, awaiting_guarantee holds). |
reservation:confirmed | The booking is genuinely final. |
Event: ready
Section titled “Event: ready”widget.on("ready", () => {});The instance has mounted; its methods are live. Mirrors widget.ready,
including for a late subscriber: subscribing after the widget has already
mounted calls the handler once, immediately, the way ready.then() would.
Event: opened
Section titled “Event: opened”widget.on("opened", () => {});The popover became visible. popover / sticky modes only.
Event: closed
Section titled “Event: closed”widget.on("closed", () => {});The popover was hidden. popover / sticky modes only.
Event: step:changed
Section titled “Event: step:changed”widget.on("step:changed", (payload: { step: ServiceWidgetStep }) => {});The guest moved to another step of the funnel.
Transitions only — there is no event for the step the funnel opens on.
The widget mounts with the page, not with the guest’s attention: a
popover or sticky instance builds its whole booking tree on load and
merely hides it, so an event at mount would count page views rather than
funnel entries, and every conversion rate computed from it would be wrong
by the ratio between the two.
Take the entry from the event that already means it on your integration:
popover/sticky—opened, which fires for every path that opens the widget: youropen()call, the sticky button, a declarative trigger.inline—ready, since an inline widget is visible from the moment it mounts.
Re-entries do fire: “make another reservation” from the confirmation,
and the restart after a payment hold expires, are both real transitions
back to availability, and a funnel that hid them would under-count
second bookings.
The step is one of four the guest can reach. Internal states — a booking disabled mid-session, for instance — are not funnel steps and are not reported here.
Event: reservation:created
Section titled “Event: reservation:created”widget.on("reservation:created", (payload: { reservation: ServiceWidgetReservation }) => {});A reservation now exists server-side — including non-final outcomes
(pending approval, awaiting_guarantee holds). Never treat this as
“booked”: listen for reservation:confirmed for that.
Event: reservation:confirmed
Section titled “Event: reservation:confirmed”widget.on("reservation:confirmed", (payload: { reservation: ServiceWidgetReservation }) => {});The booking is genuinely final.
Reservation payload
Section titled “Reservation payload”Carried by reservation:created and reservation:confirmed.
| Name | Summary |
|---|---|
reservation.publicId | Stable public identifier — the join key for webhooks and the read API. |
reservation.status | Reservation status as of the event, e.g. confirmed, pending. |
reservation.date | Service date, YYYY-MM-DD. |
reservation.startTime | Start time, HH:MM. |
reservation.partySize | Number of covers. |
reservation.sectionKey | Seating-group key, or null for “any”. |
reservation.publicId
Section titled “reservation.publicId”publicId: string;Stable public identifier — the join key for webhooks and the read API.
reservation.status
Section titled “reservation.status”status: string;Reservation status as of the event, e.g. confirmed, pending.
reservation.date
Section titled “reservation.date”date: string;Service date, YYYY-MM-DD.
reservation.startTime
Section titled “reservation.startTime”startTime: string;Start time, HH:MM.
reservation.partySize
Section titled “reservation.partySize”partySize: number;Number of covers.
reservation.sectionKey
Section titled “reservation.sectionKey”sectionKey: string | null;Seating-group key, or null for “any”.
The window.ServiceWidget global
Section titled “The window.ServiceWidget global”Installed by the script tag. create() is the entry point; the rest is the original one-widget-per-page API, kept working because restaurants have that snippet live today.
| Name | Summary |
|---|---|
ServiceWidget.create() | Create a widget instance. |
ServiceWidget.open() | Open the widget the script tag mounted. |
ServiceWidget.close() | Close the widget the script tag mounted. |
ServiceWidget.isOpen() | Whether the widget the script tag mounted is currently visible. |
ServiceWidget.destroy() | Destroy every instance on the page, whatever created them, and disarm the declarative [data-service-widget-open] triggers so a later click cannot resurrect the widget. |
ServiceWidget.version | Bundle version. |
ServiceWidget.create()
Section titled “ServiceWidget.create()”create(options: ServiceWidgetCreateOptions): ServiceWidgetInstance;Create a widget instance. The modern entry point.
ServiceWidget.open()
Section titled “ServiceWidget.open()”open(): void;Open the widget the script tag mounted.
Takes no arguments because one script tag drives one restaurant: there is never a second widget it could have meant. Called before the page has finished loading, it is remembered and honoured the moment the widget mounts, so it is safe from any script position.
Does nothing when the script tag mounted an inline widget — that one is
on the page already — or when no script tag mounted anything, in which case
use create() and call open() on the instance it returns.
ServiceWidget.close()
Section titled “ServiceWidget.close()”close(): void;Close the widget the script tag mounted.
ServiceWidget.isOpen()
Section titled “ServiceWidget.isOpen()”isOpen(): boolean;Whether the widget the script tag mounted is currently visible.
ServiceWidget.destroy()
Section titled “ServiceWidget.destroy()”destroy(): void;Destroy every instance on the page, whatever created them, and disarm the
declarative [data-service-widget-open] triggers so a later click cannot
resurrect the widget.
ServiceWidget.version
Section titled “ServiceWidget.version”version: string;Bundle version.
Referenced by the signatures above. Both are closed sets — a value outside them cannot arrive.
ServiceWidgetMode
Section titled “ServiceWidgetMode”export type ServiceWidgetMode = "inline" | "popover" | "sticky";How a widget instance is presented on the host page.
inline— rendered in flow inside the host’s own container.popover— no trigger UI; the host page opens it withwidget.open().sticky— the widget renders its own floating “Book a table” button.
ServiceWidgetStep
Section titled “ServiceWidgetStep”export type ServiceWidgetStep = "availability" | "details" | "payment" | "confirmation";A step of the booking funnel, as reported by step:changed.
availability— choosing covers, seating, day and time.details— entering name and contact details.payment— capturing a card. Only where the booking requires a guarantee, so a funnel built on these has to treat it as optional.confirmation— the receipt.