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:
| Status | Colour | Selectable by an administrator |
|---|---|---|
available | its pricing category | yes |
sold | red | no |
held | amber | no |
blocked | slate | yes — 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
- Wait for
onReady— never call API methods beforeonReadyfires. - Load layout before setting availability —
setAvailability()requires a loaded layout. - 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. - Track unsaved changes — in the Designer use
onLayoutChangedto warn users before navigating away. - Handle errors — always provide an
onErrorcallback to surface issues.