SeatSquirrel
JavaScript SDK

SeatSquirrel SDK Reference

Complete API reference for the SeatSquirrel Designer and Picker SDKs

Overview

SeatSquirrel provides two powerful JavaScript SDKs for embedding interactive seat maps on your website:

  • Designer SDK - Embed a full-featured layout editor for creating and editing venue layouts
  • Picker SDK - Embed a seat selection interface for ticket purchasing and booking

Both SDKs use PostMessage-based communication with domain verification for secure, stateless embedding. Your data stays in your database - no authentication or server calls to SeatSquirrel required.

Getting Started

Installation

Include the SDK in your HTML:

<!-- Designer SDK -->
<script src="https://seatsquirrel.com/embed/seatsquirrel-designer-sdk.js"></script>

<!-- Picker SDK -->
<script src="https://seatsquirrel.com/embed/seatsquirrel-picker-sdk.js"></script>

Or download and host yourself for better control and performance.

Domain Verification

Before using either SDK, add your domain to the allowed list:

  1. Navigate to Organization → Embed Settings
  2. Click "Add Domain"
  3. Enter your full domain with protocol (e.g., https://example.com or http://localhost:3000)
  4. Save the domain

Important: Include the full URL with protocol (https:// or http://).

Designer SDK

Create and edit venue layouts directly in your application.

Quick Start

const designer = new SeatSquirrel.Designer({
  container: '#designer-container',
  onReady: function() {
    // Designer is ready
  },
  onExport: function(layoutData) {
    // Save layout to your database
    saveToDatabase(layoutData);
  }
});

Constructor

new SeatSquirrel.Designer(options)

OptionTypeRequiredDescription
containerstring | HTMLElementYesCSS selector or DOM element
baseUrlstringNoSeatSquirrel base URL (default: https://seatsquirrel.com)
primaryColorstringNoCustom primary color (hex without #)
secondaryColorstringNoCustom secondary color (hex without #)
onReadyfunctionNoCalled when designer is ready
onLayoutLoadedfunctionNoCalled when layout loads
onExportfunctionNoImportant: Called when user exports
onLayoutChangedfunctionNoCalled when unsaved changes occur
onErrorfunctionNoCalled on errors

Methods

loadLayout(layoutData)

Load an existing layout for editing.

designer.loadLayout(savedLayout);

getLayout()

Trigger export callback to get current layout.

designer.getLayout(); // Triggers onExport

setReadOnly(readOnly)

Enable or disable editing.

designer.setReadOnly(true);  // Read-only mode
designer.setReadOnly(false); // Edit mode

clearLayout()

Clear current layout and start fresh.

designer.clearLayout();

hasChanges()

Check for unsaved changes.

if (designer.hasChanges()) {
  alert('You have unsaved changes!');
}

getCurrentLayout()

Get last exported layout (not real-time).

const lastExport = designer.getCurrentLayout();

isDesignerReady()

Check if designer is ready.

if (designer.isDesignerReady()) {
  designer.loadLayout(data);
}

destroy()

Cleanup and remove event listeners.

designer.destroy();

Callbacks

onReady()

Designer is ready to receive commands.

onReady: function() {
  console.log('Designer ready');
  designer.loadLayout(layoutData);
}

onLayoutLoaded(layoutData)

Layout successfully loaded.

onLayoutLoaded: function(layoutData) {
  console.log('Loaded:', layoutData.metadata.name);
}

onExport(layoutData)

Most Important: User clicked export or getLayout() called.

onExport: async function(layoutData) {
  await saveToDatabase(layoutData);
  alert('Layout saved!');
}

onLayoutChanged(data)

Unsaved changes status changed.

onLayoutChanged: function(data) {
  document.title = data.hasChanges ? '* Editor' : 'Editor';
}

onError(error)

An error occurred.

onError: function(error) {
  console.error('Error:', error);
  alert('An error occurred');
}

Complete Example

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

  onReady: async function() {
    const layout = await fetch('/api/layouts/123').then(r => r.json());
    designer.loadLayout(layout);
  },

  onExport: async function(layoutData) {
    await fetch('/api/layouts/123', {
      method: 'PUT',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(layoutData)
    });
    alert('Saved!');
  },

  onLayoutChanged: function(data) {
    document.getElementById('save-btn').disabled = !data.hasChanges;
  }
});

// Trigger save
document.getElementById('save-btn').onclick = () => designer.getLayout();

→ Full Designer Documentation


Picker SDK

Embed interactive seat selection for ticket purchasing.

Quick Start

const picker = new SeatSquirrel.StatelessPicker({
  container: '#picker-container',
  onReady: function() {
    picker.loadLayout(layoutData);
  },
  onComplete: function(data) {
    // Handle checkout
    processPayment(data.selections);
  }
});

Constructor

new SeatSquirrel.StatelessPicker(options)

OptionTypeRequiredDescription
containerstring | HTMLElementYesCSS selector or DOM element
baseUrlstringNoSeatSquirrel base URL (default: https://seatsquirrel.com)
primaryColorstringNoCustom primary color (hex without #)
secondaryColorstringNoCustom secondary color (hex without #)
onReadyfunctionNoCalled when picker is ready
onLayoutLoadedfunctionNoCalled when layout loads
onSelectionChangefunctionNoCalled when selections change
onCompletefunctionNoImportant: Called on checkout
onErrorfunctionNoCalled on errors

Methods

loadLayout(layoutData)

Required: Load layout before users can select seats.

picker.loadLayout(layoutData);

setAvailability(availability)

Set seat/area availability by ID.

picker.setAvailability({
  seats: { 'seat-uuid-1': true, 'seat-uuid-2': false },
  areas: { 'area-uuid-1': 50 }
});

setAvailabilityByLabels(availability)

Set availability using row/seat labels.

picker.setAvailabilityByLabels({
  seats: [
    { rowLabel: 'A', seatLabel: '1', isAvailable: false },
    { rowLabel: 'A', seatLabel: '2', isAvailable: true }
  ],
  areas: [
    { areaName: 'Standing Room', availableCount: 50 }
  ]
});

setPricing(pricing)

Override layout pricing with custom prices.

picker.setPricing({
  categories: [
    { id: 'standard', name: 'Standard', price: 5000, color: '#3b82f6' }  // R50.00 in cents
  ]
});

Note: Prices must be in cents (e.g., 5000 = R50.00).

getSelections()

Request current selections (triggers onSelectionChange).

picker.getSelections();

clearSelections()

Clear all selected seats/areas.

picker.clearSelections();

getCurrentSelections()

Get last known selections without callback.

const selections = picker.getCurrentSelections();

getCurrentTotals()

Get last known totals.

const totals = picker.getCurrentTotals();
console.log(`${totals.count} items, R${totals.amount / 100}`);

getLayout()

Get loaded layout metadata.

const layout = picker.getLayout();

isPickerReady()

Check if picker is ready.

if (picker.isPickerReady()) {
  picker.loadLayout(data);
}

destroy()

Cleanup and remove event listeners.

picker.destroy();

Callbacks

onReady()

Picker is ready to receive commands.

onReady: function() {
  picker.loadLayout(layoutData);
}

onLayoutLoaded(layoutData)

Layout successfully loaded.

onLayoutLoaded: function(layoutData) {
  console.log('Loaded:', layoutData.metadata.name);
}

onSelectionChange(data)

User selected or deselected seats/areas.

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

Selection Data Structure:

{
  selections: [
    {
      type: 'seat',
      id: 'seat-uuid-1',
      rowLabel: 'A',
      seatLabel: '1',
      pricingCategoryId: 'standard',
      price: 5000  // In cents
    }
  ],
  totals: {
    count: 1,
    amount: 5000  // R50.00 in cents
  }
}

onComplete(data)

Most Important: User clicked checkout button.

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(error)

An error occurred.

onError: function(error) {
  console.error('Error:', error);
  alert('An error occurred');
}

Complete Example

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

  onReady: async function() {
    // Load layout
    const layout = await fetch('/api/layouts/123').then(r => r.json());
    picker.loadLayout(layout);

    // Set availability
    const availability = await fetch('/api/layouts/123/availability').then(r => r.json());
    picker.setAvailability(availability);
  },

  onSelectionChange: function(data) {
    document.getElementById('count').textContent = data.totals.count;
    document.getElementById('total').textContent = `R${(data.totals.amount / 100).toFixed(2)}`;
  },

  onComplete: async function(data) {
    await fetch('/api/checkout', {
      method: 'POST',
      body: JSON.stringify(data)
    });
    alert('Checkout successful!');
  }
});

→ Full Picker Documentation


Data Structures

DesignerOutput

The layout data structure used by both Designer and Picker:

{
  type: 'seatSquirrelLayout',
  version: '1.0.0',
  layout: {
    rows: SeatRow[],
    areas: Area[],
    pricingCategories: PricingCategory[],
    sections: Section[],
    drawing: {
      rectangles: Rectangle[],
      circles: Circle[],
      lines: Line[],
      textElements: TextElement[],
      backgroundColor: string
    },
    metadata: {
      name: string,
      description?: string,
      venueId?: string
    }
  },
  totals: {
    totalSeats: number,
    totalRows: number,
    totalAreas: number,
    totalSections: number
  }
}

PricingCategory

{
  id: string,
  name: string,
  price: number,      // In cents (e.g., 5000 = R50.00)
  color: string,      // Hex color (e.g., '#3b82f6')
  customerNotes?: string
}

Availability

// By ID
{
  seats: {
    [seatId: string]: boolean  // true = available, false = sold
  },
  areas: {
    [areaId: string]: number   // Available count
  }
}

// By Labels
{
  seats: [
    {
      rowLabel: string,
      seatLabel: string,
      isAvailable: boolean
    }
  ],
  areas: [
    {
      areaName: string,
      availableCount: number
    }
  ]
}

Framework Integration

React

import { useEffect, useRef } from 'react';

export function DesignerEmbed({ onSave }) {
  const containerRef = useRef(null);
  const designerRef = useRef(null);

  useEffect(() => {
    const script = document.createElement('script');
    script.src = 'https://seatsquirrel.com/embed/seatsquirrel-designer-sdk.js';
    script.async = true;

    script.onload = () => {
      designerRef.current = new window.SeatSquirrel.Designer({
        container: containerRef.current,
        onExport: (layoutData) => {
          onSave(layoutData);
        }
      });
    };

    document.body.appendChild(script);

    return () => {
      designerRef.current?.destroy();
      document.body.removeChild(script);
    };
  }, [onSave]);

  return <div ref={containerRef} style={{ width: '100%', height: '600px' }} />;
}

Vue

<template>
  <div ref="container" style="width: 100%; height: 600px;"></div>
</template>

<script>
export default {
  props: {
    onSave: Function
  },
  data() {
    return {
      designer: null
    };
  },
  mounted() {
    this.loadSDK();
  },
  beforeUnmount() {
    this.designer?.destroy();
  },
  methods: {
    async loadSDK() {
      await new Promise((resolve) => {
        const script = document.createElement('script');
        script.src = 'https://seatsquirrel.com/embed/seatsquirrel-designer-sdk.js';
        script.onload = resolve;
        document.body.appendChild(script);
      });

      this.designer = new window.SeatSquirrel.Designer({
        container: this.$refs.container,
        onExport: (layoutData) => {
          this.onSave(layoutData);
        }
      });
    }
  }
};
</script>

Best Practices

1. Container Requirements

Both SDKs require explicit container dimensions:

<!-- ✅ Good: Explicit height -->
<div id="container" style="width: 100%; height: 600px;"></div>

<!-- ❌ Bad: No height -->
<div id="container" style="width: 100%;"></div>

Recommended heights:

  • Minimum: 500px
  • Designer: 700-800px
  • Picker: 600px
  • Full-screen: Best experience

2. Wait for onReady

Always wait for onReady before calling methods:

const instance = new SeatSquirrel.Designer({
  container: '#container',
  onReady: function() {
    // Safe to call methods now
    instance.loadLayout(data);
  }
});

// ❌ Don't call methods immediately
instance.loadLayout(data);

3. Error Handling

Always implement error handling:

onError: function(error) {
  console.error('SDK error:', error);
  // Show user-friendly message
  showNotification('An error occurred. Please try again.');
  // Log to error tracking
  logToErrorService(error);
}

4. Price Format

Always use cents for prices:

// ✅ Correct: Prices in cents
{ id: 'standard', name: 'Standard', price: 5000 }  // R50.00

// ❌ Incorrect: Prices in rands
{ id: 'standard', name: 'Standard', price: 50 }

5. Cleanup on Unmount

Always cleanup when component unmounts:

// React
useEffect(() => {
  // ... setup ...
  return () => {
    instance.destroy();
  };
}, []);

// Vue
beforeUnmount() {
  this.instance?.destroy();
}

Troubleshooting

"Access Denied" Error

Solutions:

  1. Verify domain added with protocol: https://example.com
  2. Include port for localhost: http://localhost:3000
  3. Check browser console for details

SDK Not Loading

Solutions:

  1. Verify SDK script loaded successfully
  2. Check network tab for 404 errors
  3. Ensure container element exists
  4. Check JavaScript console for errors

Methods Not Working

Solutions:

  1. Wait for onReady before calling methods
  2. Check isDesignerReady() or isPickerReady()
  3. Verify correct method names and parameters
  4. Check browser console for errors

Additional Resources

Support

Need help? Contact us at support@seatsquirrel.com