SeatSquirrel
DeploymentSelf-Hosted Mode

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.

OptionTypeRequiredDescription
onReady() => voidNoCalled when picker is ready.
onLayoutLoaded() => voidNoCalled when loadLayout() succeeds. Pure confirmation signal — no payload.
onSelectionChanged(data: SelectionData) => voidNoCalled when selections change.
onComplete(data: SelectionData) => voidNoImportant: 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 }) => voidNoCalled when an error occurs.

iFrame SDK Only

These options only apply when using the iframe SDK. They are not available in standalone mode.

OptionTypeRequiredDescription
containerstring | HTMLElementYesCSS selector or DOM element for the iframe
baseUrlstringNoLocation of the SeatSquirrel app (default: https://seatsquirrel.com)
pickerUrlstringNoFull 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

MethodParametersReturns
loadLayout(layoutData)layoutData: LayoutOutputStandalone: { 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)AssignPricingCategoriesInputStandalone: { 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)ShowToastOptionsStandalone: { 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):
PropertyTypeRequiredDescription
mode'by-id' | 'by-slug' | 'dangerously-by-labels'YesHow to identify items

Additional properties for mode 'by-id':

PropertyTypeRequiredDescription
rowSeatsRecord<string, boolean>NoMap of seat ID to boolean (true = available)
areasRecord<string, number | boolean>NoMap of area ID to available count (number), or boolean (true = 1, false = 0)
tableSeatsRecord<string, boolean>NoMap of table seat ID to boolean (true = available)
tablesRecord<string, boolean>NoMap 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, the slug field is used only for matching — it is not merged into the category. To change a category's slug, use 'update-by-id' mode and pass slug as an update field.

Parameters

  • options (Object):
PropertyTypeRequiredDescription
mode'replace' | 'update-by-id' | 'update-by-slug'YesHow to apply the pricing categories
pricingCategoriesArrayYesArray of pricing category objects (shape depends on mode)

Category Object Properties (mode: 'replace'):

PropertyTypeRequiredDescription
idstringYesUnique identifier
labelstringYesDisplay label
slugstringYesURL-friendly identifier
pricenumberYesPrice in cents
colorstringYesHex color code
customerNotesstringNoOptional notes visible to customers

Update Object Properties (mode: 'update-by-id'):

PropertyTypeRequiredDescription
idstringYesID of the category to update (must match existing)
labelstringNoNew display label
slugstringNoNew slug
pricenumberNoNew price in cents
colorstringNoNew hex color
customerNotesstringNoNew customer notes

Update Object Properties (mode: 'update-by-slug'):

PropertyTypeRequiredDescription
slugstringYesSlug of the category to match (used for lookup only, not merged)
labelstringNoNew display label
pricenumberNoNew price in cents
colorstringNoNew hex color
customerNotesstringNoNew 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

PropertyTypeRequiredDescription
mode'by-id' | 'by-slug' | 'dangerously-by-labels'YesHow to identify objects and categories

Additional properties for mode 'by-id':

PropertyTypeRequiredDescription
rowsRecord<string, string[]>NoMap of row ID to category IDs
rowSeatsRecord<string, string[]>NoMap of seat ID to category IDs
areasRecord<string, string[]>NoMap of area ID to category IDs
tablesRecord<string, string[]>NoMap of table ID to category IDs
tableSeatsRecord<string, string[]>NoMap 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':

PropertyTypeRequiredDescription
rowsArray<{ sectionId?, rowLabel, pricingCategorySlugs }>NoRow assignments by label
rowSeatsArray<{ sectionId?, rowLabel, seatLabel, pricingCategorySlugs }>NoSeat assignments by label
areasArray<{ sectionId?, areaLabel, pricingCategorySlugs }>NoArea assignments by label
tablesArray<{ sectionId?, tableLabel, pricingCategorySlugs }>NoTable assignments by label
tableSeatsArray<{ sectionId?, tableLabel, seatLabel, pricingCategorySlugs }>NoTable 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):
PropertyTypeRequiredDescription
titlestringYesMain toast message
typestringNo'success', 'info', 'warning', 'error', or 'default' (default: 'default')
descriptionstringNoSecondary description text
actionObjectNoAction 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 modeaction.onClick runs directly when the button is clicked.
  • iFrame SDK modeaction.onClick is 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, onConfirm and onCancel work the same way. In iframe mode, the SDK matches callbacks via internal correlation IDs transparently.

Parameters

  • options (Object):
PropertyTypeRequiredDescription
titlestringYesDialog title text
descriptionstringNoDialog body/description text
confirmTextstringNoConfirm button label (default: 'OK')
cancelTextstringNoCancel button label (default: 'Cancel')
variantstringNo'default' or 'danger' — danger shows a red AlertTriangle icon (default: 'default')
modestringNo'alert' (OK button only) or 'confirm' (OK/Cancel buttons) (default: 'alert')
onConfirmfunctionNoCalled when the user clicks the confirm button
onCancelfunctionNoCalled 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 when loadLayout() was called (before any user selections). Returns null if 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):
PropertyTypeRequiredDescription
mode'current' | 'last-saved'YesWhich 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