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. data.lastSelection is the item just selected/modified, or null on deselect/clear. |
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. |
requirePricingCategory | boolean | No | When true, entities without a valid pricing category are treated as unavailable. Default: true. Can also be set via loadLayout options, setPricingCategories, or setAvailability by-pricing-category mode. |
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, options?) | layoutData: LayoutOutput, options? | Standalone: { success: boolean, availabilityErrors?: string[] } iFrame: Promise<{ success: boolean, availabilityErrors?: string[] }> |
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 }> |
setSelections(input, options?) | SelectionData or SetSelectionsInput, { mode? } | Standalone: SetSelectionsResult iFrame: Promise<SetSelectionsResult> |
clearSelections() | — | { success: boolean } |
setAllowProceed(allow) | boolean | Standalone: { success: boolean } iFrame: void |
showToast(options) | ShowToastOptions | Standalone: { success: boolean } iFrame: Promise<{ actionClicked: boolean }> |
showAlertDialog(options) | ShowAlertDialogOptions | { success: boolean } |
showNotice(options) | ShowNoticeOptions | { success: boolean, noticeId: string } |
removeNotice(noticeId) | string | { success: boolean } |
destroy() | — | void (iFrame SDK only) |
loadLayout(layoutData, options?)
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 databaseoptions(Object, optional):
| Property | Type | Required | Description |
|---|---|---|---|
requirePricingCategory | boolean | No | When true, entities without a valid pricing category are treated as unavailable. Default: true. Pass false to allow uncategorized entities to remain selectable. |
initialAvailability | SetAvailabilityInput | No | Apply availability atomically with the layout load to prevent a flash of default (all-available) state. Accepts the same input as setAvailability() — any of the 5 modes. Applied after the layout is set but before onLayoutLoaded fires. |
Returns
{ success: boolean, availabilityErrors?: string[] } — success refers to the layout load itself.
availabilityErrors is only present if initialAvailability was provided and had issues (the layout still loads and onLayoutLoaded still fires).
In standalone mode this is synchronous. In iframe SDK mode it returns a Promise.
Example
// Standalone mode (synchronous)
// By default, requirePricingCategory is true — entities without a valid pricing category are unavailable
const { success } = SeatSquirrel.picker.loadLayout(layoutData);
// Opt out of pricing enforcement — uncategorized entities remain selectable
const { success } = SeatSquirrel.picker.loadLayout(layoutData, {
requirePricingCategory: false,
});
// Load with initial availability (prevents flash of default state)
const { success, availabilityErrors } = SeatSquirrel.picker.loadLayout(layoutData, {
initialAvailability: {
mode: 'by-id',
rowSeats: { 'seat-1': true, 'seat-2': false },
areas: { 'area-1': 50 },
},
});
if (availabilityErrors) {
console.warn('Availability issues:', availabilityErrors);
}
// Load with initial availability using pricing categories
const result = SeatSquirrel.picker.loadLayout(layoutData, {
initialAvailability: {
mode: 'by-pricing-category',
available: ['category-vip', 'category-standard'],
},
});// Iframe SDK mode (async)
const { success } = await picker.loadLayout(layoutData);
// Opt out of pricing enforcement
const { success } = await picker.loadLayout(layoutData, { requirePricingCategory: false });
// Load with initial availability (prevents flash)
const { success, availabilityErrors } = await picker.loadLayout(layoutData, {
initialAvailability: {
mode: 'by-slug',
rowSeats: { 'row-a/seat-1': true, 'row-a/seat-2': false },
},
});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.'by-pricing-category'— Bulk-set availability based on pricing category IDs. Entities are resolved to available/unavailable based on their assigned pricing categories (including row→seat and table→tableSeat inheritance).'by-pricing-category-slug'— Same as'by-pricing-category', but using pricing category slugs instead of IDs.
Parameters
options(Object):
| Property | Type | Required | Description |
|---|---|---|---|
mode | 'by-id' | 'by-slug' | 'dangerously-by-labels' | 'by-pricing-category' | 'by-pricing-category-slug' | 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.
Additional properties for modes 'by-pricing-category' / 'by-pricing-category-slug':
| Property | Type | Required | Description |
|---|---|---|---|
available | string[] | No | Category IDs (or slugs) whose entities should be available. Unlisted categories → entities unavailable. This is a full reset. |
unavailable | string[] | No | Category IDs (or slugs) whose entities should be unavailable. Only flips an entity to unavailable when all of its effective category IDs are in this list. Unlisted → unchanged. This is a delta. |
requirePricingCategory | boolean | No | When provided, updates the requirePricingCategory picker option. When true, uncategorized entities are treated as unavailable. |
When both available and unavailable are provided, available is processed first (full reset), then unavailable overrides: entities whose entire set of effective category IDs are in the unavailable list are flipped to unavailable.
Uncategorized entities (empty pricingCategoryIds) are always left unchanged by these modes, unless requirePricingCategory is also set to true (which makes them unavailable at the rendering level).
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,
},
});
// By pricing category — only "standard" category entities are available (full reset)
picker.setAvailability({
mode: 'by-pricing-category',
available: ['price_abc123'], // Standard category ID
});
// By pricing category — make "vip" entities unavailable (delta, others unchanged)
picker.setAvailability({
mode: 'by-pricing-category',
unavailable: ['price_def456'], // VIP category ID
});
// By pricing category — combined: available sets baseline, unavailable overrides
picker.setAvailability({
mode: 'by-pricing-category',
available: ['price_abc123', 'price_def456'], // Standard + VIP available
unavailable: ['price_abc123'], // Then override: Standard-only entities → unavailable
});
// By pricing category slug — same as above but using slugs
picker.setAvailability({
mode: 'by-pricing-category-slug',
available: ['standard', 'vip'],
});
// With requirePricingCategory — uncategorized entities also become unavailable
picker.setAvailability({
mode: 'by-pricing-category',
available: ['price_abc123'],
requirePricingCategory: 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. Entities assigned to omitted categories automatically render as unavailable and are blocked from selection (the "rendering safety net" — no additionalsetAvailabilitycall needed). This relies on therequirePricingCategorysetting (defaulttruefromloadLayout).'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) |
requirePricingCategory | boolean | No | Override whether entities must have a valid pricing category to be selectable. If omitted, inherits the current setting (default true from loadLayout). |
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 },
],
});
// Tier filtering: only offer VIP and Standard for this performance.
// Entities assigned to other categories (e.g., Budget) become unavailable
// because requirePricingCategory defaults to true via loadLayout.
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' },
],
});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);
}setSelections(input, options?)
Description
Programmatically set selections in the picker. Use this to restore a previous selection state (e.g., from a saved cart) or to pre-select items on behalf of the user.
The engine validates each item against the loaded layout — checking that the entity exists, is available, and that the
pricing category is valid when the entity has pricing categories assigned. Uncategorized bookable entities can be
selected with categoryId: null / omitted categorySlug. Valid items are applied even when some items fail
(best-effort semantics).
Two input modes:
by-id(default) — Pass fullSelectionItemobjects with machine UUIDs. Backward compatible with the legacy input shape.by-slug— Pass simplifiedSlugSelectionItemobjects with human-readable slugs. The engine resolves slugs to IDs and enriches the fullSelectionIteminternally. Recommended for integrations that don't track internal UUIDs.
The options.mode parameter controls how new selections interact with existing ones:
'replace'(default) — Clears all existing selections before applying the new ones.'merge'— Adds to existing selections without clearing.
After selections are applied, onSelectionChanged fires automatically with the updated state.
Parameters
input: EitherSelectionData(legacy/by-id) orSetSelectionsInput(discriminated bymode):
| Input Mode | Shape | Description |
|---|---|---|
by-id (default) | { selections: SelectionItem[] } or { mode: 'by-id', selections: SelectionItem[] } | Full SelectionItem objects with machine IDs |
by-slug | { mode: 'by-slug', selections: SlugSelectionItem[] } | Simplified objects with human-readable slugs |
options(Object, optional):
| Property | Type | Required | Description |
|---|---|---|---|
mode | 'replace' | 'merge' | No | How to apply selections (default: 'replace') |
Returns
SetSelectionsResult
{ success: boolean, applied: SelectionItem[], failed: SetSelectionsFailedItem[] } — In standalone mode this is
synchronous. In iframe SDK mode it returns a Promise.
successistrueonly if all items were applied (no failures).appliedcontains the items that were successfully selected.failedcontains each failed item with areasonexplaining why.
Example — by-slug mode
// Standalone mode — pre-select items using slugs
const result = SeatSquirrel.picker.setSelections({
mode: 'by-slug',
selections: [
{ type: 'row-seat', slug: 'row-a-seat-1', categorySlug: 'standard' },
{ type: 'area', slug: 'vip-lounge', categorySlug: 'vip', quantity: 2 },
{ type: 'table-seat', slug: 'table-1-seat-3', categorySlug: 'premium' },
{ type: 'whole-table', tableSlug: 'table-5', categorySlug: 'standard', quantity: 4 },
],
});
if (!result.success) {
result.failed.forEach(f => {
console.warn(`Failed: ${f.selection.slug || f.selection.id} — ${f.reason}`);
});
}
// Iframe SDK mode — by-slug (async)
const result = await picker.setSelections({
mode: 'by-slug',
selections: [
{ type: 'row-seat', slug: 'row-a-seat-1', categorySlug: 'standard' },
{ type: 'row-seat', slug: 'row-b-seat-5', categorySlug: null },
],
});Example — by-id mode
// Standalone mode — restore a saved cart (replace mode, default)
const result = SeatSquirrel.picker.setSelections({
selections: [
{ id: 'seat-id-1', type: 'row-seat', categoryId: 'cat-1' },
{ id: 'seat-id-2', type: 'row-seat', categoryId: null },
{ id: 'area-id-1', type: 'area', categoryId: 'cat-2', quantity: 2 },
],
});
if (!result.success) {
result.failed.forEach(f => {
console.warn(`Failed: ${f.selection.id} — ${f.reason}`);
});
}
// Merge mode — add to existing selections
const mergeResult = SeatSquirrel.picker.setSelections(
{
selections: [{ id: 'seat-id-3', type: 'row-seat', categoryId: 'cat-1' /* ...other fields */ }],
},
{ mode: 'merge' }
);
// Iframe SDK mode (async)
const result = await picker.setSelections({
selections: savedCart.selections,
lastSelection: null,
totals: { count: 0, amount: 0 },
});
console.log('Applied:', result.applied.length, 'Failed:', result.failed.length);clearSelections()
Description
Clear all selected seats and areas.
Returns
{ success: boolean }
Example
picker.clearSelections();setAllowProceed(allow)
Description
Enable or disable the proceed button. When disabled, the button is greyed out regardless of whether items are selected. Useful for enforcing external validation (e.g., terms acceptance) before allowing the user to proceed.
The proceed button is enabled by default.
Parameters
allow(boolean):trueto enable the proceed button,falseto disable it.
Returns
Standalone: { success: boolean } — iFrame: void (fire-and-forget).
Example
// Disable the proceed button
picker.setAllowProceed(false);
// Re-enable it after external validation passes
picker.setAllowProceed(true);showToast(options)
Description
Show a toast notification inside the picker UI.
Parameters
options(Object):
| Property | Type | Required | Description |
|---|---|---|---|
title | string | Yes | Main toast message |
variant | string | No | 'info', 'warning', 'error', or 'success' (default: 'info') |
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!', variant: 'success' });
// Per-toast action callback (works in both modes)
const { actionClicked } = await picker.showToast({
title: 'Availability changed',
variant: '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 | 'info', 'warning', 'error', or 'success' — each with distinct icon and color (default: 'info') |
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: 'error',
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: 'error',
onConfirm: function () {
window.location.reload();
},
});showNotice(options)
Description
Show a persistent, non-blocking notice banner in the picker UI. Notices remain visible until dismissed by the user or removed via removeNotice(). One notice per position — calling showNotice() with the same position replaces the existing notice (the old notice's onDismiss is not called on replacement).
Parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
options.position | string | Yes | — | 'stage-top', 'cart-top', or 'cart-before-proceed' |
options.variant | string | No | 'info' | 'info', 'warning', 'error', or 'success' |
options.title | string | Yes | — | Main notice message |
options.description | string | No | — | Optional secondary text |
options.dismissable | boolean | No | true | Whether the user can dismiss via X button |
options.action | object | No | — | { label: string, onClick?: () => void } — optional action button |
options.onDismiss | function | No | — | Called when user dismisses (not called on API removal or replacement) |
Positions:
stage-top— Banner overlaying the top of the canvas stage. Use for high-visibility alerts (e.g. "Booking closes in 5 min").cart-top— Top of the selection panel, below the header. Use for selection-context notices (e.g. "Max 4 seats per booking").cart-before-proceed— In the cart footer, above the Proceed button. Use for checkout-blocking warnings (e.g. "Select at least 2 seats").
Returns
{ success: boolean, noticeId: string } — noticeId can be passed to removeNotice().
Example
// Show a warning over the stage
const { noticeId } = picker.showNotice({
position: 'stage-top',
variant: 'warning',
title: 'Booking closes in 5 minutes',
dismissable: false,
});
// Show an info notice at the top of the cart
picker.showNotice({
position: 'cart-top',
variant: 'info',
title: 'Maximum 4 seats per booking',
});
// Show an error with an action button before the Proceed button
picker.showNotice({
position: 'cart-before-proceed',
variant: 'error',
title: 'Please select at least 2 seats',
action: {
label: 'Help',
onClick: function () {
console.log('Help clicked');
},
},
});
// Replace the stage-top notice (same position replaces)
picker.showNotice({
position: 'stage-top',
variant: 'success',
title: 'Seats reserved!',
});removeNotice(noticeId)
Description
Remove a notice by its ID. The notice's onDismiss callback is not called when removed via API — only when the user clicks the X button.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
noticeId | string | Yes | The noticeId returned by showNotice() |
Returns
{ success: boolean } — true if the notice was found and removed.
Example
const { noticeId } = picker.showNotice({
position: 'stage-top',
variant: 'info',
title: 'Loading availability...',
dismissable: false,
});
// Later, remove it programmatically
picker.removeNotice(noticeId);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