SeatSquirrel
DeploymentSelf-Hosted Mode

Standalone Mode

Integrate SeatSquirrel as a full-page app with direct JavaScript API

Overview

In Standalone mode, SeatSquirrel runs as a full-page application. There is no iFrame and no SDK file — you configure the Designer or Picker by editing the HTML file directly and setting a window.SeatSquirrelConfig object.

After the app initialises, a global window.SeatSquirrel object exposes the API.

Designer

Quick Start

Open designer.html and edit the <script> block that sets window.SeatSquirrelConfig:

<script>
  window.SeatSquirrelConfig = {
    // Wait for the designer to be ready to receive commands including load layout data
    onReady: function () {
      console.log('Designer ready!');
      // Load an existing layout from your API
      // fetch('/api/layouts/123')
      //   .then(r => r.json())
      //   .then(data => SeatSquirrel.designer.loadLayout(data));
    },
    
    onLayoutChanged: function (data) {
      // Layout was changed
      console.log('Has unsaved changes:', data.hasChanges);
    },

    onPricingCategoriesChanged: function (data) {
      // Handle in-Designer pricing changes — call getPricingCategories() for the data
      const { pricingCategories } = SeatSquirrel.designer.getPricingCategories();
      console.log('Pricing changed:', pricingCategories);
    },
  
    onError: function (error) {
      // Handle error
      console.error('Error:', error.message);
    },
    
    onSave: function (data) {
      // Save the layout to your backend
      console.log('Save layout data:', data.layout);
    }
  };
</script>

Designer Complete Example

<!DOCTYPE html>
<html>
  <head>
    <title>Venue Layout Designer</title>
  </head>
  <body>
    <div id="seatsquirrel-designer-root"></div>

    <script>
      window.SeatSquirrelConfig = {
        onReady: function () {
          console.log('Designer ready');
        },

        onSave: async function (data) {
          await fetch('/api/layouts', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify(data.layout),
          });
          SeatSquirrel.designer.showToast({
            title: 'Layout saved',
            type: 'success',
          });
        },

        onLayoutChanged: function (data) {
          document.title = data.hasChanges
            ? '* Venue Layout Designer'
            : 'Venue Layout Designer';
        },

        onError: function (error) {
          console.error(error);
        },
      };
    </script>

    <!-- Bundled app script (do not modify) -->
    <script type="module" src="/assets/standalone-designer.js"></script>
  </body>
</html>

For the full list of constructor options, methods, and callbacks see the Designer API Reference.


Picker

Quick Start

Open picker.html and edit the <script> block that sets window.SeatSquirrelConfig:

<script>
  window.SeatSquirrelConfig = {
    onReady: function () {
      console.log('Picker ready!');
      // Load your layout and set availability
      // fetch('/api/layouts/123')
      //   .then(r => r.json())
      //   .then(data => {
      //     SeatSquirrel.picker.loadLayout(data);
      //     SeatSquirrel.picker.setAvailability({ mode: 'by-slug', ...availabilityData });
      //   });
    },

    onSelectionChanged: function (data) {
      console.log('Selected:', data.totals.count, 'items');
      console.log('Total:', data.totals.amount, 'cents');
      // data.lastSelection is the item just selected/modified, or null on deselect/clear
      if (data.lastSelection) {
        console.log('Last selected:', data.lastSelection.type, data.lastSelection.id);
      }
    },

    onComplete: function (data) {
      // Handle checkout
      console.log('Checkout:', data.selections);
    },

    onError: function (error) {
      console.error('Error:', error.message);
    },
  };
</script>

Picker Complete Example

<!DOCTYPE html>
<html>
  <head>
    <title>Select Your Seats</title>
  </head>
  <body>
    <div id="seatsquirrel-picker-root"></div>

    <script>
      window.SeatSquirrelConfig = {
        onReady: async function () {
          // Fetch layout and availability together, then load atomically to prevent
          // a flash of default (all-available) state
          const [layout, availability] = await Promise.all([
            fetch('/api/layouts/123').then(r => r.json()),
            fetch('/api/availability/123').then(r => r.json()),
          ]);
          SeatSquirrel.picker.loadLayout(layout, {
            initialAvailability: { mode: 'by-slug', ...availability },
          });
        },

        onSelectionChanged: function (data) {
          document.getElementById('cart-count').textContent =
            data.totals.count;
          document.getElementById('cart-total').textContent =
            '$' + (data.totals.amount / 100).toFixed(2);
        },

        onComplete: async function (data) {
          const response = await fetch('/api/checkout', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({
              selections: data.selections,
              total: data.totals.amount,
            }),
          });
          if (response.ok) {
            window.location.href = '/checkout/success';
          }
        },

        onError: function (error) {
          console.error(error);
        },
      };
    </script>

    <!-- Bundled app script (do not modify) -->
    <script type="module" src="/assets/standalone-picker.js"></script>
  </body>
</html>

For the full list of constructor options, methods, and callbacks see the Picker API Reference.

Fullscreen

Both the Designer and the Picker ship with a fullscreen toggle button that expands the page content to fill the browser viewport. This is viewport-fill, not the browser Fullscreen API: the browser's own chrome stays visible and no user permission is required. Pressing Escape exits.

Hide the built-in button via ui.fullscreenButton: false:

window.SeatSquirrelConfig = {
  ui: { fullscreenButton: false },
  onFullscreenChange: (isFullscreen) => console.log('Fullscreen:', isFullscreen),
  // ...
};

Trigger fullscreen imperatively at runtime:

SeatSquirrel.picker.setFullscreen(true);
SeatSquirrel.designer.setFullscreen(false);

Price category filter

The Picker ships with a "Filter categories" control (top-left of the canvas). It lists the pricing categories in the current view; checking one or more fades non-matching bookables so a booker can find a price tier quickly. It is purely visual — dimmed objects stay selectable — and auto-hides when a view has fewer than two categories.

It is shown by default. Hide it via ui.pricingCategoryFilter: false:

window.SeatSquirrelConfig = {
  ui: { pricingCategoryFilter: false },
  // ...
};

Admin mode

A back-office variant of the Picker for staff rather than customers: it shows every bookable's operational status and lets an administrator select seats for a bulk action the host page owns (blocking seats off, for example).

Enable it with an admin block, then push statuses with setSeatStatuses():

window.SeatSquirrelConfig = {
  admin: {
    enabled: true,
    // optional: override any of the three non-available fills (#RRGGBB only)
    statusColors: { blocked: '#6B7280' },
  },
  onLayoutLoaded: () => {
    fetch(`/api/admin/seat-state/${eventId}`)
      .then((r) => r.json())
      .then((state) => {
        SeatSquirrel.picker.setSeatStatuses({ mode: 'by-id', rowSeats: state.rowSeats });
      });
  },
};

Each bookable carries one of four statuses. Anything with no entry reads as available:

StatusColourSelectable by an administrator
availableits pricing categoryyes
soldredno
heldamberno
blockedslateyes — so a block can be lifted

Alongside the colours, admin mode swaps the pricing hover tooltip for a status one (showing whatever tooltip lines the host supplied), and drops per-item prices, the cart total and the Proceed button — the host page owns the action the selection feeds. Read the staged selection with getSelections() when your own button is pressed.

Status is the single source of truth for colour and selectability in this mode, so do not also call setAvailability() — the two will disagree. Statuses live alongside the layout rather than inside it, so getLayout() output is unaffected and operational state can never be saved into a layout; loadLayout() clears them.

Best Practices

  1. Wait for onReady — never call API methods before onReady fires.
  2. Load layout before setting availabilitysetAvailability() requires a loaded layout.
  3. Use slugs — slug-based modes (setAvailability({ mode: 'by-slug' }), setPricingCategories({ mode: 'update-by-slug' })) are recommended over label-based alternatives since slugs are stable, human-readable identifiers you control. You may also use ID-based identifiers which are globally unique ids set by the Designer during layout creation.
  4. Track unsaved changes — in the Designer use onLayoutChanged to warn users before navigating away.
  5. Handle errors — always provide an onError callback to surface issues.

On this page