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
.htmlfile 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:3000Format: Comma-separated list of full URLs with protocol
SEATSQUIRREL_APPROVED_DOMAINS=https://example.com,https://www.example.com,http://localhost:3000Important Notes:
- Include the full URL with protocol (
https://orhttp://) - 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
- Verify domain is in
SEATSQUIRREL_APPROVED_DOMAINSwith correct protocol - Include port for localhost:
http://localhost:3000 - Check browser console for specific error details
Container Stays Blank
- Ensure container has an explicit height (not auto)
- Verify the SDK script loaded successfully (check Network tab)
- Check browser console for JavaScript errors
- Verify domain is on the allowed list
onSave / onComplete Not Firing
- Ensure the user clicks Save / Proceed in the UI, or call
getLayout({ mode: 'current' })programmatically - Verify the callback is defined in the constructor options
- Check browser console for errors