Designer API Reference
Complete API reference for the SeatSquirrel Designer
Constructor Options
new SeatSquirrel.Designer(options)
Shared Callbacks (Both Modes)
These options work in both standalone and iframe SDK modes.
| Option | Type | Required | Description |
|---|---|---|---|
permissions | DesignerPermissions | No | Restrict which features are enabled in the designer. Default: all features enabled. |
onReady | () => void | No | Called when designer is ready. |
onLayoutLoaded | () => void | No | Called when loadLayout() succeeds. Pure confirmation signal — no payload. |
onSave | (data: { layout: LayoutOutput }) => void | No | Called when user clicks Save. |
onLayoutChanged | (data: { hasChanges: boolean }) => void | No | Called when unsaved changes status changes. |
onPricingCategoriesChanged | (data: { hasChanges: boolean }) => void | No | Called when pricing changes; call getPricingCategories() for data. |
onError | (error: { message: string, code: string, details?: string }) => void | No | Called when an error occurs. See Error Codes below. |
ui | { fullscreenButton?: boolean } | No | UI configuration. ui.fullscreenButton: false hides the built-in fullscreen toggle. Default: true. You can still trigger fullscreen programmatically via setFullscreen() when the button is hidden. |
onFullscreenChange | (isFullscreen: boolean) => void | No | Called whenever the designer enters or exits viewport-fill fullscreen. |
onRequestSectionContent | (sectionId: string) => SectionContent | Promise<SectionContent> | No | Sections only. Lazily provide one Section's contents when the user drills in to edit it. Required to edit sectioned layouts — without it, drilling into a section loaded from the parent layout is blocked (its host-stored contents are never wiped). |
onSaveSectionContent | (sectionId: string, content: SectionContent) => void | Promise<void> | No | Sections only. Persist one Section's edited contents to your per-section store. See when it fires below. |
onDeleteSectionContent | (sectionId: string) => void | Promise<void> | No | Sections only. Remove a deleted Section's content blob from your per-section store. Called on save for sections the user deleted. Without it, deleted sections leave orphaned blobs in your store. |
onSectionChange | (activeSectionId: string | null) => void | No | Sections only. Fires when the user drills into a Section (its id) or back to the parent (null). |
Desktop only — The Designer requires a mouse or trackpad. On touch-only devices (phones, tablets without an
external pointer), a "Desktop Required" screen is shown and onError fires with code MOBILE_NOT_SUPPORTED. The
onReady callback will not fire and the API will not be available. Connecting an external mouse or trackpad to a
tablet will allow the designer to initialize. Narrow screens (below 768px) are also blocked regardless of input
device.
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) |
designerUrl | string | No | Full URL override for the designer page. Takes precedence over baseUrl. Useful for servers requiring .html extensions (e.g., https://example.com/designer.html) |
Restricting Designer Features
Use the permissions option to control which tools and features are available in the designer.
Disable pricing category management
When you supply pricing categories from your external system, you typically want to prevent users from creating or modifying them:
Standalone mode (window.SeatSquirrelConfig):
window.SeatSquirrelConfig = {
permissions: {
properties: {
pricingCategoriesCrud: false,
settingsPanels: { currency: false },
},
},
onReady: function () {
SeatSquirrel.designer.setPricingCategories({
mode: 'replace',
pricingCategories: [
{ id: 'vip', label: 'VIP', slug: 'vip', price: 15000, color: '#8b5cf6' },
{ id: 'standard', label: 'Standard', slug: 'standard', price: 5000, color: '#3b82f6' },
],
});
},
onSave: function (layout) {
console.log('Layout saved:', layout);
},
};iFrame SDK mode (new SeatSquirrel.Designer()):
const designer = new SeatSquirrel.Designer({
container: '#designer',
permissions: {
properties: {
pricingCategoriesCrud: false,
settingsPanels: { currency: false },
},
},
onReady: () => {
designer.setPricingCategories({
mode: 'replace',
pricingCategories: [
{ id: 'vip', label: 'VIP', slug: 'vip', price: 15000, color: '#8b5cf6' },
{ id: 'standard', label: 'Standard', slug: 'standard', price: 5000, color: '#3b82f6' },
],
});
},
});Hide individual settings panels
The Settings tab contains five panels: Pricing Categories, Background, Display, Alignment, and Currency. Use
properties.settingsPanels to hide any of them individually. Omitted panels stay visible, so you only list the ones you
want hidden. When every panel is hidden, the Settings tab itself is hidden.
const designer = new SeatSquirrel.Designer({
container: '#designer',
permissions: {
properties: {
settingsPanels: {
background: false,
display: false,
alignment: false,
// pricingCategories and currency omitted → still visible
},
},
},
});Note: the Currency panel is shown only when settingsPanels.currency is not false. Editing within the Pricing
Categories panel is still governed separately by pricingCategoriesCrud.
Read-only preview mode
// Standalone
window.SeatSquirrelConfig = {
permissions: { readOnly: true },
};
// iFrame SDK
const designer = new SeatSquirrel.Designer({
container: '#designer',
permissions: { readOnly: true },
});Assignment-only mode
Allows pricing category assignment while blocking all structural and visual edits:
- Pricing category assignment remains enabled
- Creating, moving, resizing, and deleting objects is disabled
- Undo/redo, copy/paste, and destructive controls are hidden
- Settings tab and category CRUD are disabled
- Save and export remain available
- Selected elements show greyed-out (non-editable) properties below the pricing editor
- If both
readOnlyandassignmentOnlyaretrue,readOnlytakes precedence
// Standalone
window.SeatSquirrelConfig = {
permissions: { assignmentOnly: true },
};
// iFrame SDK
const designer = new SeatSquirrel.Designer({
container: '#designer',
permissions: { assignmentOnly: true },
});
// Show greyed-out structural properties alongside the pricing panel for context
window.SeatSquirrelConfig = {
permissions: {
assignmentOnly: true,
properties: { showProperties: true },
},
};Limit available tools
// Only allow rows and areas (hide tables, shapes, text, sections)
// Standalone
window.SeatSquirrelConfig = {
permissions: {
tools: {
table: false,
shape: false,
text: false,
traceImage: false,
section: false,
},
},
};
// iFrame SDK
const designer = new SeatSquirrel.Designer({
container: '#designer',
permissions: {
tools: {
table: false,
shape: false,
text: false,
traceImage: false,
section: false,
},
},
});Hide the Settings tab
// Standalone
window.SeatSquirrelConfig = {
permissions: {
properties: {
settingsTab: false,
},
},
};
// iFrame SDK
const designer = new SeatSquirrel.Designer({
container: '#designer',
permissions: {
properties: {
settingsTab: false,
},
},
});Suppress pricing category panel warning
Hide the amber "Missing Pricing Categories" warning box shown in the properties panel when nothing is selected:
// Standalone
window.SeatSquirrelConfig = {
permissions: {
warnings: {
pricingCategoriesAssignmentPanelWarning: false,
},
},
};
// iFrame SDK
const designer = new SeatSquirrel.Designer({
container: '#designer',
permissions: {
warnings: {
pricingCategoriesAssignmentPanelWarning: false,
},
},
});Methods
Summary
| Method | Parameters | Returns |
| --------------------------------------------------------------------- | ------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------- |
| loadLayout(layoutData) | layoutData: LayoutOutput | Standalone: { success: boolean }
iFrame: Promise<{ success: boolean }> |
| getLayout({ mode }) | { mode: 'current' \| 'last-saved' } | Standalone: { layout: LayoutOutput \| null }
iFrame: Promise<{ layout: LayoutOutput \| null }> |
| hasChanges() | — | { hasChanges: boolean } |
| setPermissions(permissions) | DesignerPermissions | { success: boolean } |
| clearLayout() | — | { 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? }> |
| 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 } |
| isDesignerReady() | — | { designerReady: boolean } |
| setFullscreen(value) | boolean | { success: boolean } |
| destroy() | — | void | iFrame SDK only |
loadLayout(layoutData)
Description
Load an existing layout into the designer for editing.
Parameters
layoutData(LayoutOutput): The layout JSON from a previous export
Older layout exports that used layout.sections for label groups and sectionId on rows/areas/tables are accepted and
normalized to the current layout.types/typeId format during load.
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.designer.loadLayout(savedLayout);
// Iframe SDK mode (async)
const { success } = await designer.loadLayout(savedLayout);
console.log('Layout loaded:', success);getLayout({ mode })
Description
Get layout data from the designer. The mode parameter controls which snapshot is returned:
'current'— Returns the real-time layout state including any unsaved changes.'last-saved'— Returns the last saved or loaded layout snapshot (not real-time). Returnsnullif no layout has been saved or loaded.
Note:
getLayout({ mode: 'current' })does not trigger theonSavecallback.onSaveonly fires when the user clicks the Save button in the UI.
Parameters
options(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 }>. When mode is 'current', layout is
always present. When mode is 'last-saved', layout may be null.
Example
// Standalone mode (synchronous)
const { layout } = SeatSquirrel.designer.getLayout({ mode: 'current' });
// Iframe SDK mode (async)
const { layout } = await designer.getLayout({ mode: 'current' });
console.log(layout);
// Get the last saved snapshot
const { layout: savedLayout } = designer.getLayout({ mode: 'last-saved' });
if (savedLayout) {
console.log('Last saved:', savedLayout);
}setPermissions(permissions)
Description
Set designer permissions at runtime. Use this to dynamically enable/disable features such as read-only mode, tool restrictions, or properties panel controls.
This method goes through the same permissions system as the constructor permissions option, so all permission fields
are supported.
Parameters
permissions(DesignerPermissions): Permissions object to apply
Returns
{ success: boolean }
Example
// Enable read-only mode
designer.setPermissions({ readOnly: true });
// Disable read-only mode
designer.setPermissions({ readOnly: false });
// Enable assignment-only mode (pricing assignment only)
designer.setPermissions({ assignmentOnly: true });
// Disable specific tools at runtime
designer.setPermissions({ tools: { row: false, table: false } });
// Lock down properties panel
designer.setPermissions({
properties: { pricingCategoriesCrud: false, settingsPanels: { currency: false } },
});clearLayout()
Description
Clear the current layout and start with a blank canvas.
Returns
{ success: boolean }
Example
designer.clearLayout();setPricingCategories({ mode, ... })
Description
Set or update pricing categories in the designer. The mode parameter controls how the categories are applied:
'replace'— Replace all pricing categories with the provided array. Marks them as "externally managed" — users can only modify colors in the designer UI.'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.'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.
Note: When using
'replace'mode, the designer UI restricts users to only modifying category colors. They cannot add, delete, or change pricing details.
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 for the category |
label | string | Yes | Display label (e.g., "VIP", "Standard") |
slug | string | Yes | URL-friendly identifier |
price | number | Yes | Price in cents (e.g., 15000 for $150) |
color | string | Yes | Hex color code (e.g., "#8b5cf6") |
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 an existing category) |
label | string | No | New display label |
slug | string | No | New slug |
price | number | No | New price in cents |
color | string | No | New hex color code |
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 code |
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 result = SeatSquirrel.designer.setPricingCategories({
mode: 'replace',
pricingCategories: [
{ id: 'vip', label: 'VIP', slug: 'vip', price: 15000, color: '#8b5cf6' },
{ id: 'standard', label: 'Standard', slug: 'standard', price: 5000, color: '#3b82f6' },
],
});
console.log(result.success); // true
// Replace all categories (iframe SDK, async)
const result = await designer.setPricingCategories({
mode: 'replace',
pricingCategories: [
{ id: 'vip', label: 'VIP', slug: 'vip', price: 15000, color: '#8b5cf6' },
{ id: 'standard', label: 'Standard', slug: 'standard', price: 5000, color: '#3b82f6' },
{ id: 'economy', label: 'Economy', slug: 'economy', price: 2500, color: '#10b981' },
],
});
console.log('Pricing set:', result.success);
// Partially update by ID
const result = SeatSquirrel.designer.setPricingCategories({
mode: 'update-by-id',
pricingCategories: [
{ id: 'price_abc123', price: 7500, label: 'Standard (updated)' },
{ id: 'price_def456', color: '#10B981' },
],
});
// Partially update by slug
const result = await designer.setPricingCategories({
mode: 'update-by-slug',
pricingCategories: [
{ slug: 'standard', price: 7500, label: 'Standard (updated)' },
{ slug: 'vip', color: '#10B981' },
],
});getPricingCategories()
Description
Get the current pricing categories from the designer.
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.designer.getPricingCategories();
// Iframe SDK mode (async)
const { pricingCategories } = await designer.getPricingCategories();
console.log(pricingCategories);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' and 'by-slug':
Each property is a Record<key, categoryKeys[]> map (keys are IDs or slugs depending on mode).
| Property | Type | Required | Description |
|---|---|---|---|
rows | Record<string, string[]> | No | Row assignments |
rowSeats | Record<string, string[]> | No | Seat assignments |
areas | Record<string, string[]> | No | Area assignments |
tables | Record<string, string[]> | No | Table assignments |
tableSeats | Record<string, string[]> | No | Table seat assignments |
Additional properties for mode 'dangerously-by-labels': Each property is an array of label-based entries with
pricingCategorySlugs.
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.designer.assignPricingCategories({
mode: 'by-slug',
rows: {
'row-a': ['vip', 'standard'],
'row-b': ['standard'],
},
areas: {
'vip-lounge': ['vip'],
},
});
// Assign by ID
const result = await designer.assignPricingCategories({
mode: 'by-id',
rows: {
'row-uuid-1': ['cat-uuid-1', 'cat-uuid-2'],
'row-uuid-2': ['cat-uuid-2'],
},
});
if (!result.success) {
console.error('Assignment errors:', result.errors);
}showToast(options)
Description
Show a toast notification inside the designer 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 } |
Returns
- Standalone mode:
{ success: boolean } - iFrame SDK mode:
Promise<{ actionClicked: boolean }>— resolves when the toast is dismissed or the action button is clicked
Example
designer.showToast({ title: 'Layout saved successfully', variant: 'success' });
// Per-toast action callback (works in both modes)
const { actionClicked } = await designer.showToast({
title: 'External changes detected',
variant: 'info',
description: 'Pricing was updated in your ticketing system.',
action: {
label: 'Reload pricing',
onClick: function () {
fetchLatestPricing().then(categories =>
designer.setPricingCategories({ mode: 'replace', pricingCategories: categories })
);
},
},
});
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 designer 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
designer.showAlertDialog({
title: 'Unsaved changes',
description: 'You have unsaved changes that will be lost. Continue?',
variant: 'error',
confirmText: 'Discard changes',
cancelText: 'Go back',
onConfirm: function () {
navigateAway();
},
});
designer.showAlertDialog({
title: 'Layout published',
description: 'Your layout is now live and available for booking.',
mode: 'alert',
});showNotice(options)
Description
Show a persistent, non-blocking notice banner in the designer 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.
Designer limitation: Only
'stage-top'is supported. Cart positions ('cart-top','cart-before-proceed') are picker-only and will return{ success: false }.
Parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
options.position | string | Yes | — | Only 'stage-top' in Designer |
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) |
Returns
{ success: boolean, noticeId: string } — noticeId can be passed to removeNotice().
Example
const { noticeId } = designer.showNotice({
position: 'stage-top',
variant: 'info',
title: 'Auto-save enabled',
description: 'Changes are saved automatically every 30 seconds.',
});
// Later, remove it
designer.removeNotice(noticeId);removeNotice(noticeId)
Description
Remove a notice by its ID. The notice's onDismiss callback is not called when removed via API.
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
noticeId | string | Yes | The noticeId returned by showNotice() |
Returns
{ success: boolean } — true if the notice was found and removed.
hasChanges()
Description
Check if there are unsaved changes.
Returns
{ hasChanges: boolean } — Whether there are unsaved changes
isDesignerReady()
Description
Check if the designer is ready to receive commands.
Returns
{ designerReady: boolean } — Whether the designer is ready
setFullscreen(value)
Description
Expand the designer to fill the browser viewport, or restore it. This drives the same state as the built-in fullscreen
button and respects the ui.fullscreenButton option — calling it imperatively still works when the button is hidden.
This is viewport-fill fullscreen, not the browser Fullscreen API: the designer covers the browser window via CSS,
browser chrome stays visible, and no permissions chain is required. Pressing Escape always exits.
In iframe SDK mode, the SDK resizes the iframe element on the host page and locks document.body scroll until exit. In
standalone mode, the page root is expanded directly.
Parameters
value(boolean):trueto enter fullscreen,falseto exit
Returns
{ success: boolean }
Example
document.getElementById('my-fullscreen-button').addEventListener('click', () => {
designer.setFullscreen(true);
});destroy()
iFrame SDK only. Not available in standalone mode — standalone cleanup is handled automatically by the page lifecycle.
Description
Destroy the designer instance. Removes the iframe from the DOM, detaches the PostMessage event listener, and clears all internal state (pending requests, cached layouts, callback maps). Call this when you no longer need the designer — for example, when a modal closes or the user navigates away in a SPA.
Returns
void
Example
designer.destroy();Error Codes
The onError callback receives an object with a code string. Known codes:
| Code | Description |
|---|---|
MOBILE_NOT_SUPPORTED | The device is touch-only (no mouse/trackpad) or the viewport is too narrow (below 768px). The designer requires a pointer device. onReady will not fire and the API will not be exposed. |
NO_DATA | loadLayout() was called without providing layout data. |
Sections (drill-in layouts)
A Section is a frame on the canvas holding its own nested sub-layout (rows, areas, tables). Parent-view sections
show a double-click hint, including after content has been added. Designers double-click a section to drill in and
edit it, then drill back out by pressing Escape or clicking the empty canvas outside the section frame. This keeps
large venues (think 100k-seat stadiums) responsive: the layout you pass to / get back from the designer carries only
lightweight section shells (frame, thumbnail, seat counts), and each section's full contents are streamed lazily
to and from your per-section store. There is no SeatSquirrel database in self-hosted mode, so you own that store.
Three callbacks wire editing of sectioned layouts (both standalone and iframe):
onRequestSectionContent(sectionId)— return that section's contents on drill-in. May be async. Return empty content ({ rows: [], rowSeats: [] }) for a section your store doesn't have yet; throw/reject to abort the drill-in. Without this handler, drilling into a section loaded from the parent layout is blocked so its host-stored contents can never be silently wiped. If you return rows, each row must have at least one matching entry inrowSeats. (Sections drawn during the current session are still editable — their contents start in memory.)onSaveSectionContent(sectionId, content)— persist a section's edited contents. May be async; reject to surface a save failure (it's logged, never fatal).onDeleteSectionContent(sectionId)— remove a deleted section's content blob. Called on save for sections the user deleted. May be async. Without it, deleted sections leave orphaned blobs in your store.onSectionChange(activeSectionId)— optional; fires on drill-in (the id) or back out (null).
When contents are persisted
onSaveSectionContent fires in two situations, and onDeleteSectionContent fires once, so by the time you store the
parent layout your per-section store is consistent with it:
- On drill-out — when the user leaves a section (
Escape, a click outside the section frame, or Save/getLayoutwhile drilled in), that section's current contents are saved immediately. - On Save /
getLayout({ mode: 'current' })— any section changed at the parent scope without a drill-out is flushed too. This covers newly drawn sections (a section box that absorbed existing rows) and moved sections (dragging a section frame translates its contents). Deleted sections are removed viaonDeleteSectionContentat this point — deferred to save so it stays atomic with the parent layout that drops the shell.
Drawing a section that absorbs seats and then saving without ever drilling in still persists those seats — the flush-on-save covers it.
Format versioning: section content saved by the designer includes a
formatVersionfield. Store it opaquely with the rest of the content and return it unchanged fromonRequestSectionContent— it tells the designer/picker which serialized format the content uses so older content can be upgraded automatically on load. Content returned without aformatVersionis treated as legacy and run through all format migrations (correct for anything saved before the field existed). If you hand-author section content, include the current version to opt out of that legacy conversion.
How persistence splits
| What | Where it lives | How you get / set it |
|---|---|---|
| Parent layout (section shells only — frames, thumbnails, seat counts) | Your layout store | onSave / getLayout returns it; loadLayout takes it |
| Each section's full contents (rows/areas/tables) | Your per-section store | onSaveSectionContent writes; onRequestSectionContent reads |
The parent layout returned by onSave / getLayout does not include section contents — only the refreshed shells.
Persist the two stores together, keyed by section id, and feed them back the same way on load.
Example (standalone)
const sectionStore = new Map(); // sectionId -> SectionContent (your backend in real use)
window.SeatSquirrelConfig = {
onRequestSectionContent: sectionId => sectionStore.get(sectionId) ?? { rows: [], rowSeats: [] },
onSaveSectionContent: (sectionId, content) => {
sectionStore.set(sectionId, content);
},
onDeleteSectionContent: sectionId => {
sectionStore.delete(sectionId);
},
onSave: ({ layout }) => {
// `layout` carries section shells only — store it as your parent layout.
saveParentLayout(layout);
},
onReady: () => SeatSquirrel.designer.loadLayout(parentLayout),
};The iframe SDK exposes the same onRequestSectionContent / onSaveSectionContent / onDeleteSectionContent /
onSectionChange options on new SeatSquirrel.Designer({...}); contents flow over PostMessage, and a request that gets
no reply within ~15s rejects (clearing the loading overlay / surfacing a save or delete error).
Next Steps
- Standalone Quick Start — Direct JavaScript API without iFrame
- iFrame Quick Start — Embed via SDK
- Picker API Reference — Picker SDK documentation
- Types Reference — All shared data structures