nisebookings docs

Widget reference

Two dependency-free custom elements turn any HTML page into a booking page:

Both render into an open shadow DOM, read their configuration from data-* attributes, and talk to the public API directly from the browser. There is no iframe, no build step and no npm package.

Contents

Embedding

<script src="https://app.nisebookings.com/widget.js" defer></script>
<nise-booking data-key="YOUR_PUBLIC_KEY"></nise-booking>
<script src="https://app.nisebookings.com/events.js" defer></script>
<nise-events data-key="YOUR_PUBLIC_KEY"></nise-events>

Language

The customer-facing texts ship in Dutch and English. Each element resolves its language on its own, in this order:

  1. data-lang on the element — trimmed and lowercased; anything starting with nl is Dutch, everything else English. Use it to run one page, or one embed, in another language than the account.
  2. The account's language — Instellingen → Algemeen → Taal van het boekingsformulier (Nederlands/Engels; locale in PATCH /api/v1/settings). It arrives with GET services.
  3. Dutch, when neither is set.

<nise-booking data-key="…" data-lang="en"> sets lang on its host element (the shadow tree inherits it) and re-renders the current step when data-lang changes; <nise-events data-lang="en"> reads the attribute on connect and again after the first fetch. On WordPress the plugin shortcodes take the same value: [nise_booking lang="en"] and [nise_events lang="en"] ("nl" for Dutch); without it they follow the account language.

Not translated on purpose: server data (service, staff and event titles and descriptions) and numbers and dates. Formatted values follow the account's locale, so an English widget on a Dutch account shows Dutch month names and € 1.234,56 — set the account language as well if you want everything in English.

The API base URL

The widget decides once, at script-load time, where the API lives:

  1. data-api="https://app.nisebookings.com" if present — used verbatim (no trailing-slash normalisation);
  2. otherwise the origin of the script's own src (https://app.nisebookings.com/widget.js → https://app.nisebookings.com);
  3. otherwise the embedding page's origin.

If you load the scripts from anywhere other than https://app.nisebookings.com, data-api is mandatory. Note that document.currentScript is null for module scripts, inline scripts and dynamically injected async scripts; in those cases the fallback is the page origin, so set data-api explicitly.

Requests without an Origin header work too (server-to-server), but for browser embeddings your account's Toegestane websites list applies (Instellingen → Insluiten). Empty list = any site; otherwise the API answers 403 origin_not_allowed.

<nise-booking> attributes

Attribute Default Meaning
data-key — Required. Your account's public key.
data-api script origin API base URL (see above).
data-service — Preselect a service id (from GET services). The widget skips the service step only when the service has fewer than 2 staff (or hides staff), no extras, capacity 1, the chain feature is off and the entry is not already in the cart.
data-staff any Preselect a staff id, or any. Ignored when the chosen service is not offered by that staff member (falls back to any).
data-package — Package mode for an already bought package: the token from the /p/<token> link.
data-lang account language Language of the customer-facing texts: nl or en (anything not starting with nl means English). See Language.

Attributes are read on connect only — except data-lang, which the element observes: changing it after the element is in the DOM switches the language (and the host's lang attribute) and re-renders. No other attribute has an attributeChangedCallback, and re-inserting the same element does not re-initialise it. If you need a different configuration, create a new element (or replace the old one).

Booking flow step by step

Steps are 0 services, 1 time, 2 details/payment, 3 done, 4 payment-status message, 5 cart.

Step 0 — Kies een dienst. Service list with title, description, duration and price; packages show "N dagen geldig"/"onbeperkt geldig". A staff <select> ("Medewerker", first option "Maakt niet uit") appears only when the service has more than one public staff member and the business has not hidden staff for collaborative services. "Aantal personen" appears when capacity > 1. Extras render as checkboxes (single-choice) or number inputs; mandatory extras are pre-set and disabled. Buttons: Kies een tijd (packages: Verder), Dienst toevoegen (chain), Deze dienst weglaten / Geen dienst toevoegen, Terug naar je afspraken (cart). Empty: "Er zijn nu geen diensten te boeken."

Step 1 — Kies een tijd. One week at a time with Vorige week / Volgende week (hasMore from the API decides whether "next" is enabled). Slots are grouped per day with localised headings. Occupancy is shown only when Bezetting tonen is on and capacity > 1: 3/8 ("Bezette plaatsen") or nog 5 ("Vrije plaatsen"). Waiting-list slots are greyed with "wachtlijst (N)". Loading text "Tijden laden…", empty "Geen vrije tijden in deze week. Kijk een week verder.", failure "De tijden kunnen nu niet worden geladen. Probeer het opnieuw."

Step 5 — Je afspraken (cart; shown when winkelmand is on or the entry is recurring). Lists each entry with service, extras, day/time, staff, "· herhaald" for series, and per entry Wijzigen, Verwijderen and, when the service allows repeats, Herhalen / Herhaling aanpassen. Footer: Verder and Nog een afspraak.

Step 2 — Je gegevens. Fields Naam, E-mail, Telefoon, Opmerking, then the service's own intake questions (Instellingen → Diensten → Velden: text, textarea, choice, checkbox, number, date or file — JPG/PNG/WebP/PDF up to 5 MB, uploaded before the booking is sent; a required one is marked, a per-person one renders a control per person as "Persoon 1", "Persoon 2", and a conditional one (Toon alleen als) appears and disappears as the customer answers the question it depends on — see the public API's fields and {{fields}} in mails); then, when something has to be paid: the coupon field (Toepassen), payment method radios (Ter plaatse betalen / Online betalen (iDEAL, creditcard en meer) — only when the business offers both), and, for Mollie orders where any service has a deposit other than 100%, the choice Alleen de aanbetaling / Het hele bedrag. A live price summary (refreshed through POST quote, 250 ms debounce) shows Totaal, plus Subtotaal and Korting only when a coupon applies, a VAT line ("waarvan btw" when prices include VAT, "Btw" otherwise) only when VAT is non-zero, "Nu te betalen" / "Later te betalen" for deposits and "Ter plaatse te betalen (herhaalde afspraken)" when part of a series is paid on site. Submit: Afspraak bevestigen, or Naar betalen for an online payment. Notes appear for waiting list ("Je komt op de wachtlijst, je betaalt nu niets.") and package bookings ("Gebruik het e-mailadres waarmee je het pakket hebt gekocht.").

Step 3 — done. Heading and text follow the result: "Je afspraak staat vast" when every timed item is approved, "Je aanvraag is ontvangen" when something is still pending, "Je staat op de wachtlijst" for waiting-list-only orders, "Je pakket is besteld" for package purchases. Nog een afspraak maken restarts (in package mode it reloads the package).

Error handling is inline and specific:

Situation Message
Slot taken between choosing and submitting (409) "Een gekozen tijdstip is net geboekt. Kies een ander tijdstip." — the entry is highlighted, or reopened for editing when it was the only one.
Validation (422) Per-field messages ("Vul je naam in.", "Vul een geldig e-mailadres in.", "Vul een geldig telefoonnummer in.", "Je opmerking is te lang.") + "Controleer de gemarkeerde velden."; focus moves to the first invalid field.
Invalid coupon "Deze code is ongeldig of al gebruikt." / "Deze code is verlopen." — the field is cleared and the price re-quoted.
Package errors "Dit pakket hoort bij een ander e-mailadres.", "Dit pakket is verlopen.", "Dit pakket is opgebruikt."
Waiting list full "De wachtlijst voor dit tijdstip is vol. Kies een ander tijdstip."
Mollie unavailable (502) "Online betalen lukt nu even niet. Kies ter plaatse betalen of probeer het later opnieuw."
Anything else "Er ging iets mis bij het boeken. Probeer het opnieuw."
Services unreachable "De agenda kan nu niet worden geladen. Probeer het later opnieuw."

Package mode

<nise-booking data-key="…" data-package="<token>"> (this is what /p/<token> renders) loads GET packages/<token> and locks the widget to that package:

Recurrence, cart and chain in the widget

Waitlist and occupancy

Waiting-list slots appear only when the business enabled the waiting list and the service allows it. A slot row shows "wachtlijst (N)" where N is the number of people already waiting. Booking one creates a waitlisted booking without payment; when a seat frees up the customer receives the waitlist_offer mail with a first-come-first-served /w/<token> link.

Payment step and return handling

For an online (Mollie) payment the widget redirects to the returned checkoutUrl. On return, with nb_pay=<token> in the URL, it removes that parameter (history.replaceState), shows "Betaling controleren…" and polls GET payments/<token> every 2 seconds, up to 15 times:

You do not need a thank-you page of your own when you use the widget: pass a returnUrl on your page and the widget handles the return. When you build a custom checkout, do the same polling yourself — see 04-public-api.md.

<nise-events>

Attributes: data-key, data-api and data-lang (read on connect and again after the first fetch; see Language).

Styling

The shadow DOM isolates internal styles; the host exposes design tokens as CSS custom properties. Set them on the element (or any ancestor):

nise-booking, nise-events {
  --nb-accent: #E5006F;        /* buttons, links, highlights */
  --nb-accent-hover: #C2005E;
  --nb-text: #1A1A1F;
  --nb-text-2: #4A4B68;
  --nb-text-3: #6E6F88;        /* muted meta text */
  --nb-bg: #fff;
  --nb-sunken: #F7F8FB;        /* cards, panels */
  --nb-border: #E6E7EE;
  --nb-border-strong: #C7C6D1;
  --nb-danger: #CF2E36;
  --nb-focus: #FF4D9B;         /* focus ring */
  --nb-radius: 4px;
}

Accessibility

WordPress

No plugin is needed. In the block editor, add a Custom HTML block to any page or post and paste the two lines from Embedding (widget.js + <nise-booking>, or events.js + <nise-events>). The data-service, data-staff and other attributes work the same way, data-lang included. The plugin's shortcodes take the language as lang: [nise_booking lang="en"] and [nise_events lang="en"] (or "nl"; without it, the account's language). Your WordPress user must be allowed to add scripts (administrators are, by default). Add the WordPress site to Toegestane websites in the booking app, or leave that list empty.

Limits, caching and versioning

Limit Value
Items per order 60 (server), chain parts 10 (server); the widget itself adds no cap
Chain/search window 31 days per slots call (the widget asks one week at a time)
Recurrence 2–52 occurrences, interval 1–30
Name / email 255 characters
Phone 50 characters
Notes 2000 characters
Coupon code the widget's field allows 50; the API accepts 40
Ticket quantity 1–50 per type, at most 20 types
Script sizes (budget) widget.js ≤ 20 KB gzip (≈16.5 KB now), events.js ≤ 8 KB gzip (≈5.8 KB now); both languages ship in the same file

What the widget cannot do