Picker API Reference
Complete API reference for the SeatSquirrel Picker
Constructor Options
new SeatSquirrel.StatelessPicker(options)
Shared Callbacks (Both Modes)
These options work in both standalone and iframe SDK modes.
| Option | Type | Required | Description |
|---|---|---|---|
onReady | () => void | No | Called when picker is ready. |
onLayoutLoaded | () => void | No | Called when loadLayout() succeeds. Pure confirmation signal — no payload. |
onSelectionChanged | (data: SelectionData) => void | No | Called when selections change. |
onComplete | (data: SelectionData) => void | No | Important: Called when user clicks to finalise their selection. This is the most important callback — where you handle the user's final selections and send them to your backend for processing. |
onError | (error: { message: string, code: string, details?: string }) => void | No | Called when an error occurs. |
iFrame SDK Only
These options only apply when using the iframe SDK. They are not available in standalone mode.
| Option | Type | Required | Description |
|---|---|---|---|
container | string | HTMLElement | Yes | CSS selector or DOM element for the iframe |
baseUrl | string | No | Location of the SeatSquirrel app (default: https://seatsquirrel.com) |
pickerUrl | string | No | Full URL override for the picker page. Takes precedence over baseUrl. Useful for servers requiring .html extensions (e.g., https://example.com/picker.html) |
Methods
Summary
| Method | Parameters | Returns |
|---|---|---|
loadLayout(layoutData) | layoutData: LayoutOutput | Standalone: { success: boolean } iFrame: Promise<{ success: boolean }> |
isPickerReady() | — | { pickerReady: boolean } |
setAvailability({ mode, ... }) | { mode, ... } | { success: boolean } |
setPricingCategories({ mode, ... }) | { mode, pricingCategories } | Standalone: { success: boolean } iFrame: Promise<{ success: boolean }> |
getPricingCategories() | — | Standalone: { pricingCategories: PricingCategory[] } iFrame: Promise<{ pricingCategories: PricingCategory[] }> |
assignPricingCategories(input) | AssignPricingCategoriesInput | Standalone: { success, errors? } iFrame: Promise<{ success, errors? }> |
getSelections() | — | SelectionData |
getTotals() | — | { count: number, amount: number } |
getLayout({ mode }) | { mode: 'current' | 'last-saved' } | Standalone: { layout: LayoutOutput | null } iFrame: Promise<{ layout: LayoutOutput | null }> |
clearSelections() | — | { success: boolean } |
showToast(options) | ShowToastOptions | Standalone: { success: boolean } iFrame: Promise<{ actionClicked: boolean }> |
showAlertDialog(options) | ShowAlertDialogOptions | { success: boolean } |
destroy() | — | void |
loadLayout(layoutData)
Description
Load a layout into the picker for seat selection. This must be called before users can select seats.
Parameters
layoutData(LayoutOutput): The layout JSON from the designer or your database
Returns
{ success: boolean } — In standalone mode this is synchronous. In iframe SDK mode it returns a Promise<{ success: boolean }>.
Example
// Standalone mode (synchronous)
const { success } = SeatSquirrel.picker.loadLayout(layoutData);
// Iframe SDK mode (async)
const { success } = await picker.loadLayout(layoutData);
console.log('Layout loaded:', success);setAvailability({ mode, ... })
Description
Set which seats and areas are available. The mode parameter controls how items are identified:
'by-id'— Use machine-generated IDs to identify seats, areas, table seats, and tables.'by-slug'— Use user-defined slugs. This is the recommended mode as slugs are stable, human-readable identifiers that you control.'dangerously-by-labels'— Use display labels. Use with caution — labels can clash if duplicated across sections. Prefer'by-slug'for reliable lookups.
Parameters
options(Object):
| Property | Type | Required | Description |
|---|---|---|---|
mode | 'by-id' | 'by-slug' | 'dangerously-by-labels' | Yes | How to identify items |
Additional properties for mode 'by-id':
| Property | Type | Required | Description |
|---|---|---|---|
rowSeats | Record<string, boolean> | No | Map of seat ID to boolean (true = available) |
areas | Record<string, number | boolean> | No | Map of area ID to available count (number), or boolean (true = 1, false = 0) |
tableSeats | Record<string, boolean> | No | Map of table seat ID to boolean (true = available) |
tables | Record<string, boolean> | No | Map of table ID to boolean (true = available) |
Additional properties for mode 'by-slug': (AvailabilityBySlug)
Object with rowSeats, areas, tableSeats, and tables maps keyed by slug.
Additional properties for mode 'dangerously-by-labels': (AvailabilityByLabels)
Object with rowSeats, areas, tableSeats, and tables arrays.
Returns
{ success: boolean, errors?: string[] }
Example
// By ID
picker.setAvailability({
mode: 'by-id',
rowSeats: {
'row-1234567890_abc123-seat-0_1234567890': true,
'row-1234567890_abc123-seat-1_1234567890': false,
},
areas: {
'area-1234567890_abc123': 50, // number for count
'area-9876543210_def456': false, // boolean for single-customer areas (false=0, true=1)
},
tableSeats: {
'table-1234567890_abc123-seat-0_1234567890': true,
},
tables: {
'table-1234567890_abc123': false,
'table-1234567891_def456': true,
},
});
// By slug (recommended)
picker.setAvailability({
mode: 'by-slug',
rowSeats: {
'row-a-seat-1': false,
'row-a-seat-2': true,
},
areas: {
'standing-room': 50,
'vip-lounge': 0, // number: 0 = sold out
'private-booth': false, // boolean: false = unavailable (same as 0)
},
tableSeats: {
'table-1-seat-1': false,
},
tables: {
'vip-table-1': false,
'table-5': true,
},
});
// Dangerously by labels (use with caution)
picker.setAvailability({
mode: 'dangerously-by-labels',
rowSeats: [
{ rowLabel: 'A', seatLabel: '1', isAvailable: false },
{ rowLabel: 'A', seatLabel: '2', isAvailable: true },
],
areas: [
{ areaLabel: 'Standing Room', availableCount: 50 },
{ areaLabel: 'Private Booth', availableCount: false }, // boolean: false = 0
],
tableSeats: [
{ tableLabel: 'Table 1', seatLabel: '1', isAvailable: false },
{ tableLabel: 'Table 1', seatLabel: '2', isAvailable: true },
],
tables: [
{ tableLabel: 'VIP Table', isAvailable: false },
{ tableLabel: 'Table 5', isAvailable: true },
],
});setPricingCategories({ mode, ... })
Description
Set or update pricing categories in the picker. The mode parameter controls how the categories are applied:
'replace'— Replace all pricing categories with new data. This mode replaces all category properties (id,label,slug,price,color,customerNotes). Existing seat-to-category assignments are preserved.'update-by-id'— Partially update existing categories by ID. Merges provided fields into matching categories while leaving unmentioned categories untouched. All-or-nothing: if any ID doesn't match an existing category, the entire call fails with no changes applied. This is also the way to do price-only updates — just pass{ id, price }.'update-by-slug'— Partially update existing categories by slug. Merges provided fields into matching categories while leaving unmentioned categories untouched. All-or-nothing: if any slug doesn't match an existing category, the entire call fails with no changes applied. This is also the way to do price-only updates — just pass{ slug, price }.
Note: When using
'update-by-slug'mode, theslugfield is used only for matching — it is not merged into the category. To change a category's slug, use'update-by-id'mode and passslugas an update field.
Parameters
options(Object):
| Property | Type | Required | Description |
|---|---|---|---|
mode | 'replace' | 'update-by-id' | 'update-by-slug' | Yes | How to apply the pricing categories |
pricingCategories | Array | Yes | Array of pricing category objects (shape depends on mode) |
Category Object Properties (mode: 'replace'):
| Property | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Unique identifier |
label | string | Yes | Display label |
slug | string | Yes | URL-friendly identifier |
price | number | Yes | Price in cents |
color | string | Yes | Hex color code |
customerNotes | string | No | Optional notes visible to customers |
Update Object Properties (mode: 'update-by-id'):
| Property | Type | Required | Description |
|---|---|---|---|
id | string | Yes | ID of the category to update (must match existing) |
label | string | No | New display label |
slug | string | No | New slug |
price | number | No | New price in cents |
color | string | No | New hex color |
customerNotes | string | No | New customer notes |
Update Object Properties (mode: 'update-by-slug'):
| Property | Type | Required | Description |
|---|---|---|---|
slug | string | Yes | Slug of the category to match (used for lookup only, not merged) |
label | string | No | New display label |
price | number | No | New price in cents |
color | string | No | New hex color |
customerNotes | string | No | New customer notes |
Returns
{ success: boolean } — In standalone mode this is synchronous. In iframe SDK mode it returns a Promise<{ success: boolean }>.
Example
// Replace all categories (standalone, synchronous)
const { success } = SeatSquirrel.picker.setPricingCategories({
mode: 'replace',
pricingCategories: [
{ id: 'cat-1', label: 'Standard', slug: 'standard', price: 5000, color: '#3B82F6' },
{ id: 'cat-2', label: 'VIP', slug: 'vip', price: 10000, color: '#EF4444', customerNotes: 'Includes drinks' },
],
});
// Replace all categories (iframe SDK, async)
const { success } = await picker.setPricingCategories({
mode: 'replace',
pricingCategories: [
{ id: 'cat-1', label: 'Standard', slug: 'standard', price: 5000, color: '#3B82F6' },
{ id: 'cat-2', label: 'VIP', slug: 'vip', price: 10000, color: '#EF4444', customerNotes: 'Includes drinks' },
],
});
// Partially update by ID
const { success } = SeatSquirrel.picker.setPricingCategories({
mode: 'update-by-id',
pricingCategories: [
{ id: 'price_abc123', price: 7500, label: 'Standard (updated)' },
{ id: 'price_def456', color: '#10B981' },
],
});
// Partially update by slug
const { success } = await picker.setPricingCategories({
mode: 'update-by-slug',
pricingCategories: [
{ slug: 'standard', price: 7500, label: 'Standard (updated)' },
{ slug: 'vip', color: '#10B981' },
],
});
// Price-only update (pass only the price field — other properties are preserved)
SeatSquirrel.picker.setPricingCategories({
mode: 'update-by-slug',
pricingCategories: [
{ slug: 'standard', price: 4000 },
{ slug: 'vip', price: 8000 },
],
});getPricingCategories()
Description
Get the current pricing categories from the loaded layout. Returns the full category objects including id, label, slug, price, color, and customerNotes.
Returns
{ pricingCategories: PricingCategory[] } — In standalone mode this is synchronous. In iframe SDK mode it returns a Promise<{ pricingCategories: PricingCategory[] }>.
Example
// Standalone mode (synchronous)
const { pricingCategories } = SeatSquirrel.picker.getPricingCategories();
// Iframe SDK mode (async)
const { pricingCategories } = await picker.getPricingCategories();
pricingCategories.forEach(cat => {
console.log(cat.label, cat.price, cat.color);
});assignPricingCategories(input)
Description
Assign pricing categories to layout objects (rows, seats, areas, tables, table seats) programmatically. This is useful when pricing categories are managed externally and need to be mapped to layout objects after loading.
Atomic semantics: If any assignment fails validation, none are applied. All errors are returned in the errors array.
The mode parameter controls how objects and categories are identified:
'by-id'— Reference objects and categories by their internal UUIDs.'by-slug'— Reference objects and categories by their user-defined slugs. This is the recommended mode.'dangerously-by-labels'— Reference objects by human-readable labels (can be ambiguous across sections). Categories are always referenced by slug in this mode.
Parameters
input(AssignPricingCategoriesInput):
| Property | Type | Required | Description |
|---|---|---|---|
mode | 'by-id' | 'by-slug' | 'dangerously-by-labels' | Yes | How to identify objects and categories |
Additional properties for mode 'by-id':
| Property | Type | Required | Description |
|---|---|---|---|
rows | Record<string, string[]> | No | Map of row ID to category IDs |
rowSeats | Record<string, string[]> | No | Map of seat ID to category IDs |
areas | Record<string, string[]> | No | Map of area ID to category IDs |
tables | Record<string, string[]> | No | Map of table ID to category IDs |
tableSeats | Record<string, string[]> | No | Map of table seat ID to category IDs |
Additional properties for mode 'by-slug':
Same shape as 'by-id', but keys are slugs (e.g., { 'row-a': ['vip', 'standard'] }).
Additional properties for mode 'dangerously-by-labels':
| Property | Type | Required | Description |
|---|---|---|---|
rows | Array<{ sectionId?, rowLabel, pricingCategorySlugs }> | No | Row assignments by label |
rowSeats | Array<{ sectionId?, rowLabel, seatLabel, pricingCategorySlugs }> | No | Seat assignments by label |
areas | Array<{ sectionId?, areaLabel, pricingCategorySlugs }> | No | Area assignments by label |
tables | Array<{ sectionId?, tableLabel, pricingCategorySlugs }> | No | Table assignments by label |
tableSeats | Array<{ sectionId?, tableLabel, seatLabel, pricingCategorySlugs }> | No | Table seat assignments by label |
Returns
AssignPricingCategoriesResult { success: boolean, errors?: string[] } — In standalone mode this is synchronous. In iframe SDK mode it returns a Promise.
Example
// Assign by slug (recommended)
const result = SeatSquirrel.picker.assignPricingCategories({
mode: 'by-slug',
rows: {
'row-a': ['vip', 'standard'],
'row-b': ['standard'],
},
areas: {
'vip-lounge': ['vip'],
},
});
// Assign by ID
const result = await picker.assignPricingCategories({
mode: 'by-id',
rows: {
'row-uuid-1': ['cat-uuid-1', 'cat-uuid-2'],
'row-uuid-2': ['cat-uuid-2'],
},
});
// Dangerously by labels (use with caution)
const result = SeatSquirrel.picker.assignPricingCategories({
mode: 'dangerously-by-labels',
rows: [
{ rowLabel: 'A', pricingCategorySlugs: ['vip', 'standard'] },
{ sectionId: 'section-uuid', rowLabel: 'A', pricingCategorySlugs: ['economy'] },
],
});
if (!result.success) {
console.error('Assignment errors:', result.errors);
}clearSelections()
Description
Clear all selected seats and areas.
Returns
{ success: boolean }
Example
picker.clearSelections();showToast(options)
Description
Show a toast notification inside the picker UI.
Parameters
options(Object):
| Property | Type | Required | Description |
|---|---|---|---|
title | string | Yes | Main toast message |
type | string | No | 'success', 'info', 'warning', 'error', or 'default' (default: 'default') |
description | string | No | Secondary description text |
action | Object | No | Action button: { label: string, onClick?: function }. Per-toast onClick is called when the action button is clicked |
Returns
- Standalone mode:
{ success: boolean } - iFrame SDK mode:
Promise<{ actionClicked: boolean }>— resolves when the toast is dismissed or the action button is clicked
Example
picker.showToast({ title: 'Seats reserved!', type: 'success' });
// Per-toast action callback (works in both modes)
const { actionClicked } = await picker.showToast({
title: 'Availability changed',
type: 'info',
description: 'Some seats are no longer available.',
action: {
label: 'Refresh',
onClick: function () {
fetchLatestAvailability().then(a => picker.setAvailability({ mode: 'by-slug', ...a }));
},
},
});
console.log('User clicked action:', actionClicked);Action handling by mode:
- Standalone mode —
action.onClickruns directly when the button is clicked. - iFrame SDK mode —
action.onClickis called via correlation IDs when the action button is clicked. The returned Promise resolves with{ actionClicked: true }if the action was clicked, or{ actionClicked: false }if the toast was dismissed.
showAlertDialog(options)
Description
Show a modal alert dialog inside the picker UI. The dialog blocks interaction and requires the user to explicitly confirm or cancel.
Note: Only one alert dialog can be shown at a time. Calling
showAlertDialog()while a dialog is open cancels the previous one. In both modes,onConfirmandonCancelwork the same way. In iframe mode, the SDK matches callbacks via internal correlation IDs transparently.
Parameters
options(Object):
| Property | Type | Required | Description |
|---|---|---|---|
title | string | Yes | Dialog title text |
description | string | No | Dialog body/description text |
confirmText | string | No | Confirm button label (default: 'OK') |
cancelText | string | No | Cancel button label (default: 'Cancel') |
variant | string | No | 'default' or 'danger' — danger shows a red AlertTriangle icon (default: 'default') |
mode | string | No | 'alert' (OK button only) or 'confirm' (OK/Cancel buttons) (default: 'alert') |
onConfirm | function | No | Called when the user clicks the confirm button |
onCancel | function | No | Called when the user clicks the cancel button |
Returns
{ success: boolean }
Example
picker.showAlertDialog({
title: 'Seat unavailable',
description: 'This seat was just booked by another customer.',
mode: 'alert',
variant: 'danger',
onConfirm: function () {
fetchLatestAvailability().then(a => picker.setAvailability({ mode: 'by-slug', ...a }));
},
});
picker.showAlertDialog({
title: 'Session expired',
description: 'Your reservation has expired. Please start again.',
mode: 'alert',
variant: 'danger',
onConfirm: function () {
window.location.reload();
},
});getSelections()
Description
Get the current selections synchronously from cached state.
Returns
(SelectionData) { selections, totals } — current selections and totals
Example
const { selections, totals } = picker.getSelections();
console.log(`${totals.count} items selected, $${totals.amount / 100}`);getTotals()
Description
Get the last known totals without triggering a callback.
Returns
(Object) { count: number, amount: number }
Example
const totals = picker.getTotals();
console.log(`${totals.count} items, $${totals.amount / 100}`);getLayout({ mode })
Description
Get the layout by mode. The mode parameter controls what is returned:
'current'— Returns the real-time layout state with current availability and user selections reflected on all seats, areas, tables, and table seats.'last-saved'— Returns the layout snapshot from whenloadLayout()was called (before any user selections). Returnsnullif no layout has been loaded.
Explicit isAvailable on every seat
In 'current' mode, every seat and table seat has an explicit isAvailable boolean — always true or false, never undefined or omitted.
Parameters
input(Object):
| Property | Type | Required | Description |
|---|---|---|---|
mode | 'current' | 'last-saved' | Yes | Which layout snapshot to return |
Returns
{ layout: LayoutOutput | null } — In standalone mode this is synchronous. In iframe SDK mode it returns a Promise<{ layout: LayoutOutput | null }>.
Example
// Standalone mode (synchronous)
const { layout } = SeatSquirrel.picker.getLayout({ mode: 'current' });
// Iframe SDK mode (async)
const { layout } = await picker.getLayout({ mode: 'current' });
if (layout) {
layout.layout.rows.forEach(row => {
row.rowSeats.forEach(seat => {
console.log(seat.label, seat.isAvailable);
});
});
}
// Get the last loaded snapshot
const { layout: savedLayout } = picker.getLayout({ mode: 'last-saved' });
if (savedLayout) {
console.log('Last loaded:', savedLayout);
}isPickerReady()
Description
Check if the picker is ready to receive commands.
Returns
{ pickerReady: boolean } — Whether the picker is ready
destroy()
iFrame SDK only. Not available in standalone mode — standalone cleanup is handled automatically by the page lifecycle.
Description
Destroy the picker instance. Removes the iframe from the DOM, detaches the PostMessage event listener, and clears all internal state (pending requests, selections, callback maps). Call this when you no longer need the picker — for example, when a modal closes or the user navigates away in a SPA.
Returns
void
Example
picker.destroy();Next Steps
- Standalone Quick Start — Direct JavaScript API without iFrame
- iFrame Quick Start — Embed via SDK
- Designer API Reference — Designer SDK documentation
- Types Reference — All shared data structures