Embedding the Stateless Designer
Learn how to embed SeatSquirrel seat layout designer on your website
Overview
The SeatSquirrel Stateless Designer allows you to embed a full-featured seat layout editor directly into your website using the Designer SDK. Unlike the database-driven designer, the stateless version gives you complete control over where and how layout data is stored.
Key Benefits:
- Full designer experience embedded in your app
- Complete control over data storage (your database, not ours)
- PostMessage-based communication
- Domain-verified security
- No authentication required
- Export layouts as JSON for use in the picker
Quick Start
Step 1: Add Your Domain
- Navigate to Organization → Embed Settings
- Click "Add Domain"
- Enter your full domain with protocol and without a trailing slash (e.g.,
https://example.comorhttp://localhost:3000) - Save the domain
Important: Include the full URL with protocol (https:// or http://).
Self-Hosting
If you are self-hosting SeatSquirrel then you can add an approved domain to the APPROVED_DOMAINS environment
variable. See the Self-Hosting Guide for details.
Examples:
https://example.comhttps://www.example.comhttp://localhost:3000
Step 2: Include the SDK
Add the SeatSquirrel Designer SDK to your page:
<script src="https://seatsquirrel.com/embed/seatsquirrel-designer-sdk.js"></script>Or download and host it yourself for slightly better performance. Ideally this should be placed just before your closing
</body> tag so that the script is loaded after the page has finished loading.
Step 3: Create a Container
Add a container element where the designer will render:
<div id="designer-container" style="width: 100%; height: 600px;"></div>Important: The container must have an explicit height set. The designer will fill 100% of the container's dimensions.
Step 4: Initialize the Designer
<script>
const designer = new SeatSquirrel.Designer({
container: '#designer-container',
baseUrl: 'https://seatsquirrel.com',
onReady: function () {
console.log('Designer is ready');
},
onExport: function (layoutData) {
// Save the exported layout to your database
console.log('Layout exported:', layoutData);
saveToYourDatabase(layoutData);
},
onLayoutChanged: function (data) {
// Track unsaved changes
console.log('Has unsaved changes:', data.hasChanges);
},
onError: function (error) {
console.error('Designer error:', error);
},
});
</script>Ideally this should be placed just before your closing </body> tag after the container element and the sdk source
script so that the elements are loaded in the correct order.
SDK API Reference
For complete documentation on all constructor options, methods, and callbacks, see the Designer SDK API Reference.
Key Methods
| Method | Description |
|---|---|
loadLayout(data) | Load an existing layout for editing |
getLayout() | Request the current layout (triggers onExport) |
setReadOnly(bool) | Enable/disable read-only mode |
clearLayout() | Clear the layout and start fresh |
hasChanges() | Check for unsaved changes |
destroy() | Cleanup the instance |
Key Callbacks
| Callback | Description |
|---|---|
onReady() | Designer is ready to receive commands |
onExport(layoutData) | Important: User exported layout or getLayout() was called |
onLayoutChanged(data) | Unsaved changes status changed |
onError(error) | An error occurred |
Complete Example
<!DOCTYPE html>
<html>
<head>
<title>Venue Layout Editor</title>
<style>
#designer-container {
width: 100%;
height: 800px;
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>
<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"></div>
<script src="https://seatsquirrel.com/embed/seatsquirrel-designer-sdk.js"></script>
<script>
let currentLayoutId = null;
const designer = new SeatSquirrel.Designer({
container: '#designer-container',
baseUrl: 'https://seatsquirrel.com',
onReady: function () {
document.getElementById('status').textContent = 'Ready';
},
onExport: async function (layoutData) {
// Save to your backend
const response = await fetch('/api/layouts', {
method: currentLayoutId ? 'PUT' : 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
id: currentLayoutId,
data: layoutData,
}),
});
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);
},
});
// Save button
document.getElementById('save-btn').addEventListener('click', () => {
designer.getLayout(); // Triggers onExport callback
});
// Load button
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;
});
// Clear button
document.getElementById('clear-btn').addEventListener('click', () => {
if (confirm('Clear the current layout?')) {
designer.clearLayout();
currentLayoutId = null;
}
});
</script>
</body>
</html>Framework Integration
React Component
import { useEffect, useRef, useState } from 'react';
export function DesignerEmbed({ onSave, initialLayout }) {
const containerRef = useRef(null);
const designerRef = useRef(null);
const [hasChanges, setHasChanges] = useState(false);
useEffect(() => {
// Load SDK dynamically
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,
baseUrl: 'https://seatsquirrel.com',
onReady: () => {
if (initialLayout) {
designerRef.current.loadLayout(initialLayout);
}
},
onExport: layoutData => {
onSave(layoutData);
setHasChanges(false);
},
onLayoutChanged: data => {
setHasChanges(data.hasChanges);
},
});
};
document.body.appendChild(script);
return () => {
if (designerRef.current) {
designerRef.current.destroy();
}
document.body.removeChild(script);
};
}, [initialLayout, onSave]);
const handleSave = () => {
if (designerRef.current) {
designerRef.current.getLayout();
}
};
return (
<div>
{hasChanges && <div className="alert">You have unsaved changes</div>}
<button onClick={handleSave}>Save Layout</button>
<div ref={containerRef} style={{ width: '100%', height: '600px' }} />
</div>
);
}Vue Component
<template>
<div>
<div v-if="hasChanges" class="alert">You have unsaved changes</div>
<button @click="saveLayout">Save Layout</button>
<div ref="container" style="width: 100%; height: 600px;"></div>
</div>
</template>
<script>
export default {
props: {
initialLayout: Object,
onSave: Function,
},
data() {
return {
designer: null,
hasChanges: false,
};
},
mounted() {
this.loadSDK();
},
beforeUnmount() {
if (this.designer) {
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,
baseUrl: 'https://seatsquirrel.com',
onReady: () => {
if (this.initialLayout) {
this.designer.loadLayout(this.initialLayout);
}
},
onExport: layoutData => {
this.onSave(layoutData);
this.hasChanges = false;
},
onLayoutChanged: data => {
this.hasChanges = data.hasChanges;
},
});
},
saveLayout() {
if (this.designer) {
this.designer.getLayout();
}
},
},
};
</script>Best Practices
1. Container Sizing
The designer requires significant screen space for usability:
- Minimum height: 500px for basic use
- Recommended: 700-800px for comfortable editing
- Full screen: Best user experience for complex layouts
2. Unsaved Changes Tracking
Always track unsaved changes to prevent data loss:
let hasUnsavedChanges = false;
const designer = new SeatSquirrel.Designer({
// ... other options
onLayoutChanged: data => {
hasUnsavedChanges = data.hasChanges;
},
});
// Warn before leaving
window.addEventListener('beforeunload', e => {
if (hasUnsavedChanges) {
e.preventDefault();
e.returnValue = 'You have unsaved changes. Are you sure you want to leave?';
}
});3. Auto-Save
Implement auto-save for better UX:
let autoSaveTimer;
const designer = new SeatSquirrel.Designer({
// ... other options
onLayoutChanged: data => {
if (data.hasChanges) {
// Auto-save after 30 seconds of inactivity
clearTimeout(autoSaveTimer);
autoSaveTimer = setTimeout(() => {
designer.getLayout(); // Triggers onExport
}, 30000);
}
},
});4. Error Handling
Always handle errors gracefully:
const designer = new SeatSquirrel.Designer({
// ... other options
onError: error => {
console.error('Designer error:', error);
// Show user-friendly message
showErrorNotification('An error occurred in the designer. Your changes may not have been saved.');
// Optionally log to your error tracking service
logToErrorService(error);
},
});Domain Verification
The designer uses the same domain verification as the picker:
- Go to Organization → Embed Settings
- Add your domain with protocol (e.g.,
https://example.com) - The designer will verify the domain on load
- If verification fails, users see an "Access Denied" message
Troubleshooting
"Access Denied" Error
Problem: Designer shows "Access Denied"
Solutions:
- Verify domain is added with correct protocol (https:// or http://)
- Include port for localhost:
http://localhost:3000 - Check browser console for specific error details
Designer Not Loading
Problem: Container stays blank
Solutions:
- Ensure container has explicit height (not auto)
- Verify SDK file loaded successfully
- Check browser console for JavaScript errors
- Verify domain is on allowed list
onExport Not Firing
Problem: onExport callback never gets called
Solutions:
- Ensure user clicks the Export button in the designer UI
- Call
designer.getLayout()programmatically - Check for JavaScript errors in console
Changes Not Persisting
Problem: Layout changes don't save
Solutions:
- Verify
onExportcallback saves to your database - Check for errors in save operation
- Ensure layout data is being POSTed correctly
- Verify your backend API is working
Next Steps
- Designer SDK API Reference - Complete API documentation
- Picker Embedding - Embed the picker to let users select seats
- Self-Hosting Guide - Deploy on your own infrastructure