SeatMap — Floor Plan Reservation System

Upload a floor plan, draw the tables, seats or spaces, and take reservations on your own site or any other site with one script tag. Self-hosted, one-time purchase.

Overview

SeatMap is a standalone Laravel application. It has three parts:

The map builder

Requirements

ItemMinimum
PHP8.2 or newer (tested up to 8.4)
DatabaseMySQL 8.0+ or MariaDB 10.6+
PHP extensionsPDO MySQL, mbstring, OpenSSL, fileinfo, JSON, GD (or Imagick)
Web serverApache with mod_rewrite, or Nginx. HTTPS recommended (required for Stripe).
Build toolsNone. Composer dependencies and the builder bundle are included.

Installation

  1. In your hosting panel create an empty MySQL database and a user with full rights on it. Note the name, user and password.
  2. Upload Application/seatmap.zip to the server and unzip it. You get a folder named seatmap.
  3. Point the domain or subdomain at seatmap/public. In cPanel: Domains → Document Root.
  4. Open the domain in a browser. The installer starts automatically: it checks the server, asks for the database and creates your owner account.
  5. You land in the admin. Click Maps → Load example maps to see six ready-made venues, or New map to start from your own plan.
Cannot change the document root? Upload the contents of the seatmap folder into public_html instead. The included root .htaccess forwards every request to public/. Sub-folders work too: public_html/booking serves at https://your-domain.com/booking.
Nginx? Set root /path/to/seatmap/public; and the standard Laravel try_files $uri $uri/ /index.php?$query_string; location.

Folder permissions

The web server user must be able to write to storage/ and bootstrap/cache/ (and to .env during installation). The installer's first screen tells you if something is not writable.

Cron

Add one line to the server's crontab so nightly jobs run (unpaid-hold expiry, GDPR anonymisation):

* * * * * cd /path/to/seatmap && php artisan schedule:run >/dev/null 2>&1

Without it the app still works; only those background jobs are skipped.

Updating

  1. Back up the database and the storage/ folder.
  2. Unzip the new seatmap.zip over the existing files. Keep your .env and storage/ — they hold your settings and uploads.
  3. Open https://your-domain.com/admin. Pending database changes are applied on the first request; if a screen asks you to run php artisan migrate --force, run it once from the server.

Maps & the builder

A map is one floor plan with its own areas, hours and prices. Open Maps → New map, give it a name and pick a booking mode; the builder opens.

Booking modes

ModeVisitor picksTypical use
Date & timea day and a time slotrestaurants, salons, courts
Date onlya daytheatres, events, whole-day desks
Date rangea start and an end daysunbeds, rentals, long stays
Instantnothing — books for nowparking, walk-in desks

Set it under Map settings: "Require date selection", "Require time selection", "Allow date range". "Allow multi-select" lets one booking hold several areas (a row of cinema seats).

Hours & pricing

Embedding on your site

Every map has its own snippet under Details & embed. Paste it where the map should appear:

<script src="https://your-domain.com/embed.js" data-map="restaurant" async></script>
AttributeMeaning
data-mapthe map's slug (required)
data-themeauto (default), light or dark
data-fonta Google Fonts family name to match your site, e.g. Manrope
data-max-widthlimit the width in pixels and centre it
data-min-heightinitial height before the map reports its own (default 320)

The map renders in an iframe that resizes itself to its content, so it never scrolls internally. Bookings made inside it land in your admin. It does not need third-party cookies, so it books fine on any domain.

WordPress: add a "Custom HTML" block and paste the snippet. Shopify: a "Custom Liquid" section. Webflow / Wix / Squarespace: an embed / code block.

You can also link straight to the booking page: https://your-domain.com/map/restaurant, or to the front page listing all published maps.

Reservations

Reservations list

Double bookings

Every booking is checked inside a database transaction with row locks. Two people racing for the last table: one gets it, the other is told "this area has just been reserved" and the map refreshes.

Slot bookings conflict only when they share the exact same start time. If your service lasts longer than one slot (75-minute slots, 90-minute dinners) choose a slot length that matches the real duration.

Emails

Guests receive received, confirmed and cancelled emails, each with a calendar (.ics) file and a private "view or cancel your booking" link. You receive a heads-up for every new booking.

Online payments (Stripe)

  1. In Stripe Dashboard → Developers → API keys copy the Secret key.
  2. Settings → Online payments: provider Stripe Checkout, paste the secret key, set the currency code (e.g. USD, EUR, GBP) and whether to charge the full amount or a deposit percentage. Save.
  3. Optional but recommended: in Stripe → Webhooks add the endpoint shown in Settings (…/api/v1/webhooks/stripe) for the event checkout.session.completed, and paste the signing secret.
  4. In each map that should charge, tick Require online payment under Map settings.

Flow: the guest fills the form → the areas are held for 30 minutes (status "Unpaid") → Stripe Checkout opens → on success the booking becomes confirmed and the emails go out. Holds that are not paid free themselves. Confirmation works even without the webhook: the return page verifies the session with Stripe.

Use test keys (sk_test_…) first and make a booking with card 4242 4242 4242 4242. Switch to live keys when it works. HTTPS is required.

Settings & team

Dashboard

Scheduled tasks

With the cron line from the installation section, these run automatically:

TaskWhen
Expire unpaid Stripe holdsevery 5 minutes
Anonymise old reservations (if retention > 0)03:30 daily

Translation

All admin, widget and email strings are in lang/en.json. Copy it to lang/de.json (any locale code), translate the values — keep the keys and the :placeholders — and set APP_LOCALE=de in .env. Date names follow the same locale.

API

The widget talks to a small JSON API you can also use yourself:

EndpointPurpose
GET /api/v1/maps/{id}/availability?date=YYYY-MM-DD[&time=HH:MM][&date_to=…]Reserved area ids for a day, slot or range.
POST /api/v1/reservationsCreate a booking. Requires the X-SRM-Token header from the map page (a signed 12-hour token), so writes cannot be scripted from outside a page you serve.
GET /api/v1/reservations/{id}/calendar.ics?sig=…Calendar file (signed link from the confirmation).
POST /api/v1/webhooks/stripeStripe webhook (signature-checked).

Troubleshooting

The installer says something is not writable

Give the web server user write access to storage/, bootstrap/cache/ and the app folder (for .env). On most shared hosts that is already the case.

Blank page or 500 after upload

Check the PHP version (8.2+) and that public/ is the document root or the root .htaccess is in place. Details are in storage/logs/laravel.log.

Emails are not arriving

Use Settings → Email → Send test email. If it fails, the error is shown. Most hosts need SMTP with authentication; port 587 + TLS is the usual combination.

The embedded map shows but booking fails

The map page must be served over HTTPS when the host site is HTTPS, and the server clock must be roughly correct (the write token is time-limited to 12 hours).

Assets look outdated after an update

Clear any server or CDN cache. Asset URLs carry a version parameter, but some CDNs ignore query strings.

Reset the installer

Delete storage/app/installed and open the site; the wizard runs again (existing data stays unless you point it at a new database).

Support

Email support@webkoding.com with your purchase code, the PHP version and the relevant lines from storage/logs/laravel.log. Support covers installation and bugs in the delivered product; customisation is outside its scope.