SeatSquirrel
DeploymentSelf-Hosted Mode

iFrame Mode

Embed SeatSquirrel Designer and Picker via SDK with PostMessage communication

Overview

In iFrame mode, the SDK creates an iFrame on your page and handles all communication via PostMessage. This gives you CSS isolation, multiple-instance support, and cross-origin compatibility.

Designer

Step 1: Include the SDK

<script src="https://seatsquirrel.yourdomain.com/sdk/designer-sdk.js"></script>

Step 2: Add a Container

<div id="designer-container" style="width: 100%; height: 700px;"></div>

The container must have an explicit height. The designer fills 100% of the container's dimensions.

Step 3: Initialise

<script>
  const designer = new SeatSquirrel.Designer({
    container: '#designer-container',
    baseUrl: 'https://seatsquirrel.yourdomain.com',

    onReady: function () {
      console.log('Designer is ready');
    },

    onSave: function (data) {
      // Save the layout to your database
      saveToYourDatabase(data.layout);
    },

    onLayoutChanged: function (data) {
      console.log('Has unsaved changes:', data.hasChanges);
    },

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

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


Picker

Step 1: Include the SDK

<script src="https://seatsquirrel.yourdomain.com/sdk/picker-sdk.js"></script>

Step 2: Add a Container

<div id="picker-container" style="width: 100%; height: 600px;"></div>

Step 3: Initialise

<script>
  const picker = new SeatSquirrel.StatelessPicker({
    container: '#picker-container',
    baseUrl: 'https://seatsquirrel.yourdomain.com',

    onReady: function () {
      picker.loadLayout(layoutDataFromYourDatabase);
      picker.setAvailability({ mode: 'by-slug', ...availabilityFromYourDatabase });
    },

    onSelectionChanged: function (data) {
      console.log('Selected:', data.selections);
      console.log('Total:', data.totals.amount);
    },

    onComplete: function (data) {
      processCheckout(data.selections, data.totals);
    },

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

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


Using Explicit URLs

Some web servers like IIS don't perform URL rewriting and require the .html file extension. Use designerUrl and pickerUrl instead of baseUrl:

const designer = new SeatSquirrel.Designer({
  container: '#designer-container',
  designerUrl: 'https://seatsquirrel.yourdomain.com/designer.html',
  // ...
});

const picker = new SeatSquirrel.StatelessPicker({
  container: '#picker-container',
  pickerUrl: 'https://seatsquirrel.yourdomain.com/picker.html',
  // ...
});

When to use explicit URLs

Use designerUrl / pickerUrl when:

  • Your server requires .html file extensions (e.g., IIS without URL rewrite)
  • You've deployed the files to custom paths
  • You need different base URLs for designer and picker

Complete Example

<!DOCTYPE html>
<html>
  <head>
    <title>SeatSquirrel Demo</title>
    <style>
      .container {
        width: 100%;
        height: 700px;
        border: 1px solid #e5e7eb;
        border-radius: 8px;
      }
      .toolbar {
        display: flex;
        gap: 10px;
        margin-bottom: 20px;
      }
      button {
        padding: 10px 20px;
        border-radius: 6px;
        border: none;
        cursor: pointer;
      }
      .primary {
        background: #3b82f6;
        color: white;
      }
      .secondary {
        background: #e5e7eb;
        color: #374151;
      }
    </style>
  </head>
  <body>
    <h1>Venue Layout Designer</h1>

    <div class="toolbar">
      <button id="save-btn" class="primary">Save Layout</button>
      <button id="load-btn" class="secondary">Load Layout</button>
      <button id="clear-btn" class="secondary">Clear</button>
      <span id="status"></span>
    </div>

    <div id="designer-container" class="container"></div>

    <script src="https://seatsquirrel.yourdomain.com/sdk/designer-sdk.js"></script>
    <script>
      const SEATSQUIRREL_URL = 'https://seatsquirrel.yourdomain.com';
      let currentLayoutId = null;

      const designer = new SeatSquirrel.Designer({
        container: '#designer-container',
        baseUrl: SEATSQUIRREL_URL,

        onReady: function () {
          document.getElementById('status').textContent = 'Ready';
        },

        onSave: async function (data) {
          const response = await fetch('/api/layouts', {
            method: currentLayoutId ? 'PUT' : 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ id: currentLayoutId, data: data.layout }),
          });
          const result = await response.json();
          currentLayoutId = result.id;
          document.getElementById('status').textContent = 'Saved!';
          setTimeout(
            () => (document.getElementById('status').textContent = ''),
            2000,
          );
        },

        onLayoutChanged: function (data) {
          if (data.hasChanges)
            document.getElementById('status').textContent = 'Unsaved changes';
        },

        onError: function (error) {
          alert('Error: ' + error.message);
        },
      });

      document.getElementById('save-btn').addEventListener('click', () => {
        designer.getLayout({ mode: 'current' });
      });

      document
        .getElementById('load-btn')
        .addEventListener('click', async () => {
          const layoutId = prompt('Enter layout ID:');
          if (!layoutId) return;
          const response = await fetch(`/api/layouts/${layoutId}`);
          const layoutData = await response.json();
          designer.loadLayout(layoutData);
          currentLayoutId = layoutId;
        });

      document.getElementById('clear-btn').addEventListener('click', () => {
        if (confirm('Clear the current layout?')) {
          designer.clearLayout();
          currentLayoutId = null;
        }
      });
    </script>
  </body>
</html>

Best Practices

Container Sizing

  • Minimum height: 500px for designer, 400px for picker
  • Recommended: 700–800px for comfortable editing
  • Full screen: Best user experience for complex layouts

Unsaved Changes Tracking

window.addEventListener('beforeunload', e => {
  if (designer.hasChanges().hasChanges) {
    e.preventDefault();
    e.returnValue = 'You have unsaved changes.';
  }
});

Error Handling

Always provide an onError callback and log to your error tracking service.


Domain Verification

The iFrame mode uses the SEATSQUIRREL_APPROVED_DOMAINS environment variable to control which domains are allowed to embed the Designer and Picker. This is a security measure to prevent unauthorized embedding.

Important

Set the SEATSQUIRREL_APPROVED_DOMAINS environment variable before your application loads. The exact method depends on your deployment platform. For example, in a .env file:

SEATSQUIRREL_APPROVED_DOMAINS=https://example.com,https://www.example.com,http://localhost:3000

Format: Comma-separated list of full URLs with protocol

SEATSQUIRREL_APPROVED_DOMAINS=https://example.com,https://www.example.com,http://localhost:3000

Important Notes:

  • Include the full URL with protocol (https:// or http://)
  • Include the port for localhost: http://localhost:3000
  • No trailing slashes
  • Separate multiple domains with commas
  • The iFrame verifies the parent domain on load — if verification fails, users see an "Access Denied" message

Troubleshooting

"Access Denied" Error

  1. Verify domain is in SEATSQUIRREL_APPROVED_DOMAINS with correct protocol
  2. Include port for localhost: http://localhost:3000
  3. Check browser console for specific error details

Container Stays Blank

  1. Ensure container has an explicit height (not auto)
  2. Verify the SDK script loaded successfully (check Network tab)
  3. Check browser console for JavaScript errors
  4. Verify domain is on the allowed list

onSave / onComplete Not Firing

  1. Ensure the user clicks Save / Proceed in the UI, or call getLayout({ mode: 'current' }) programmatically
  2. Verify the callback is defined in the constructor options
  3. Check browser console for errors