Guides

Restaurant table booking

Let guests pick a date, time and table on a visual floor plan of your venue — embedded on your site or driven headlessly through the API.

How it works

A room (Main floor, Terrace…) is a resource of type table. Its tables are named units with a position on the floor plan, a shape and a min/max party size. Tables can be joined into combinations for larger parties. Each room has booking rules (arrival interval, seating duration by party size, buffer, deposit, no-show handling) and opening hours (one or more service windows per weekday).

A booking occupies one or more tables for a time window. Overlaps are prevented by a database constraint, so double bookings cannot happen even under concurrent load.

Set up in the dashboard

1

Create a room

Go to Setup → Floor plans (or the Floor plan tab of a table widget) and start from a template. Drag tables into place, set seats and join tables that can be combined.
2

Opening hours & rules

Add service windows (Lunch 11:30–14:00, Dinner 17:00–22:00) and tune the booking rules: arrival times every 15 min, 90–150 min seatings, optional deposit per guest.
3

Create a table widget

In Widgets, choose Table booking widget. Pick which rooms it shows, whether guests choose their own table, and publish it.

Embed

The iframe snippet includes a tiny listener so the widget grows and shrinks with its content on phones:

iframe
<iframe id="zentier-tables"
  src="https://YOUR_APP_URL/embed/TENANT_UUID?key=zentier_pk_xxx&widget=WIDGET_UUID"
  width="100%" height="720" frameborder="0" style="border:none;border-radius:12px"></iframe>
<script>
  window.addEventListener('message', function (e) {
    if (e.origin !== 'https://YOUR_APP_URL') return;
    var d = e.data || {};
    if (d.type === 'booking-widget-event' && d.event === 'resize' && d.height) {
      document.getElementById('zentier-tables').style.height = d.height + 'px';
    }
  });
</script>

With the SDK, auto-height is built in. Prefill the party size, date or room with data attributes:

SDK (inline)
<script src="https://YOUR_APP_URL/sdk/booking-widget.js"
  data-tenant="TENANT_UUID" data-key="zentier_pk_xxx" data-widget-config="WIDGET_UUID"
  data-api-url="https://YOUR_APP_URL/api" data-embed-url="https://YOUR_APP_URL"
  data-mode="inline" data-target="zentier-tables"
  data-party-size="2" data-date="2026-10-04" data-room="ROOM_RESOURCE_UUID"></script>
<div id="zentier-tables"></div>
SDK events & methods
BookingWidget.on('tableSelected', ({ unit_ids, names }) => console.log(names))
BookingWidget.on('bookingSuccess', (booking) => console.log(booking.tables))
BookingWidget.on('resize', ({ height }) => {})

// Open the modal prefilled
BookingWidget.openTableBooking({ partySize: 4, date: '2026-10-04' })

// Headless
const day = await BookingWidget.checkTableAvailability({ roomId, date: '2026-10-04', partySize: 4 })
const result = await BookingWidget.bookTable({
  roomId, startTime: day.times[0].start_utc, partySize: 4,
  customerName: 'Anna', customerEmail: 'anna@example.com',
})

API

GET/api/widget/configPublishable key
For table widgets the response includes floor_plans: canvas, decor elements, tables (position, shape, seats), combinations, booking rules and service windows.
GET/api/widget/tables/availabilityPublishable key
?room_id&date&party_size → arrival-time grid with available | limited | full per time. Add &start_time= for per-table states at that seating. ?mode=dates&start_date&end_date → open | full | closed per date.
POST/api/widget/tables/holdPublishable key
Soft-holds a table for a few minutes while the guest fills in details. Body: { room_id, unit_ids, start_time, party_size, hold_client_id }.
POST/api/widget/tables/bookingPublishable key
Creates the booking. Body: { room_id, start_time, party_size, unit_ids | null, customer_name, customer_email, … }. Free bookings return is_free: true; deposits return a Stripe/Mollie checkout. A 409 with TABLE_NO_LONGER_AVAILABLE includes alternative times.
POST/api/bookings/{'{id}'}/tablesDashboard session
Staff: move a booking to other tables or another time.

Webhooks

In addition to booking.created / booking.paid / booking.cancelled, table bookings emit booking.seated, booking.no_show, booking.completed and booking.table_assigned. See Webhooks.

Time zones

Arrival times are computed in the venue's time zone from its opening hours. The API returns both a UTC instant (start_utc) and the local label (time_local) so you never need to convert on the client.