Chart workspace layouts
A chart layout is a named snapshot of the complete chart workspace: every
chart, the active chart, symbols, intervals, chart types, indicators,
comparisons, display settings, multi-chart grid metadata, and sync settings.
Drawings are included when their persistence mode is embedded or both.
Use ChartLayout when users should reopen one specific chart workspace as they
left it. The host must mount the expected chart controllers with the same stable
chart ids before restore; applying a chart layout does not create a missing chart.
Use a template when the same configuration should be
applied elsewhere, user settings when preferences should
survive switching chart layouts, and WidgetLayoutState
for the surrounding widget panel arrangement.
What a chart layout owns
| Included in the chart layout | Stored elsewhere |
|---|---|
| Every chart's symbol, interval, chart type, indicators, comparisons, display settings, and optionally drawings | Per-user values in User settings |
| The active chart, multi-chart grid metadata, and cross-chart sync settings | Separate drawing buckets when drawing persistence is separate |
| Chart layout id, name, schema version, SDK-owned multi-chart grid metadata, and host metadata | Widget panel arrangements in WidgetLayoutState |
A public ChartLayout is versioned JSON with id, optional name,
activeChartId, charts, optional sync, and optional metadata. The SDK
reserves metadata.multiChart for the multi-chart split tree, selection,
visibility, and maximize state; preserve it when adding host metadata. Each chart
entry carries its exact symbol, interval, state, drawings, indicators,
comparisons, and display settings.
widget.chart().saveLayout() saves one chart only. It cannot preserve sibling
charts, the active chart, chart grid metadata, or cross-chart sync. Use that
low-level API only for a deliberately single-chart surface with host-owned UI.
Save and restore one chart workspace
Use widget.chartLayouts() as the canonical chart-layout authority for both
single-chart and multi-chart widgets:
import type { ChartWidgetApi } from '@tradescript/pro/sdk/core';
declare const widget: ChartWidgetApi;
const chartLayouts = widget.chartLayouts();
await chartLayouts.saveAs('opening-drive', 'Opening drive');
const summaries = await chartLayouts.list();
const summary = summaries.find((item) => item.id === 'opening-drive');
if (!summary) throw new Error('Saved chart layout was not listed');
const restored = await chartLayouts.load('opening-drive');
if (!restored) throw new Error('Saved chart layout was not found');
await chartLayouts.save('opening-drive');
await chartLayouts.rename('opening-drive', 'Opening drive — revised');
await chartLayouts.delete('opening-drive');
Verify the user-visible result at each step:
saveAsassigns the chart layout's stable logical id and display name.list()returns the saved chart layout summary.- After changing a chart or the chart grid,
load()restores saved content and grid state for the mounted charts whose ids match the saved chart entries. save()updates the same saved chart layout.rename()changes the display name without changing chart layout identity.delete()removes only that saved chart layout.load()resolvesnullwhen the chart layout does not exist and leaves the live chart workspace unchanged.
Define chart layout identity and lifecycle
| Fact | Rule |
|---|---|
ChartLayout.id | Stable logical identity for one named chart workspace |
ChartLayout.name | Human-readable label; it may change without creating a new identity |
ChartLayout.version | Saved-state schema version; currently 1 |
ChartLayout.charts | Complete per-chart entries restored together as one chart workspace |
ChartLayout.metadata | SDK-owned multiChart grid state plus optional host data; preserve SDK-owned keys and never place unrelated persisted objects here |
saveAs(layoutId, name) re-identifies the current chart workspace under the
supplied chart layout id and optional name. save(layoutId) captures later
changes under that identity. Do not derive chart layout identity from the
display name.
Deleting a chart layout must not cascade to separately stored drawings, templates, user settings, replay state, or widget panel arrangements. If the product offers cascading cleanup, make it a separate user-confirmed workflow.
When drawing persistence is embedded, the drawings share the chart layout's
lifecycle. When it is separate, the chart layout identifies the drawing
context but does not own the drawing records. See
Drawing storage before choosing the drawing lifecycle.
Restore matches ChartLayout.charts[].id to controllers already attached to
the workspace. A saved entry with no mounted match is skipped; an attached chart
with no saved entry keeps its current content. Treat chart ids as part of the
persisted-state contract and test them across application releases.
Preserve backend record identity across sessions
When a backend assigns a storageId different from ChartLayout.id, the chart
layout controller keeps that mapping in memory so updates target the existing
record. Persist and restore the mapping when host code drives chart layouts
directly:
import type { ChartWorkspaceLayoutControllerApi } from '@tradescript/pro/sdk';
declare const chartLayouts: ChartWorkspaceLayoutControllerApi;
declare function saveChartLayoutIds(ids: Record<string, string>): Promise<void>;
declare function loadChartLayoutIds(): Promise<Record<string, string> | undefined>;
chartLayouts.restoreStorageIds(await loadChartLayoutIds());
await chartLayouts.saveAs('opening-drive', 'Opening drive');
await saveChartLayoutIds(chartLayouts.getStorageIds());
Restore the mapping before a direct load(logicalId), save(logicalId),
rename(logicalId, ...), or delete(logicalId) in a later session. When
features.workspaceTabs is enabled, its user-settings record persists this
mapping together with the open tab order and active tab.
Chart layout tabs
Enable features.workspaceTabs when the product needs multiple named, open
chart workspaces. The chart layout tab strip owns create, save, duplicate,
rename, reorder, close, delete, and actions for opening saved chart layouts so
the product has one chart-layout UI authority.
When chart layout tabs are disabled, the chart storage menu exposes the chart
layout save and load actions and still routes them through
widget.chartLayouts().
loadLastChart is also widget-owned. It runs once after all chart controllers
exist, chooses the configured or newest saved chart layout, and applies
matching chart entries by chart id.
Missing and failed chart-layout operations
| Outcome | Controller behavior |
|---|---|
| Saved chart layout is missing | load(chartLayoutId) resolves null; the live chart workspace stays unchanged |
| Apply fails while creating or saving as | The controller restores the previous live chart workspace before rethrowing |
| A separate drawing load fails | The chart layout and drawing failure remain separate storage outcomes |
The chart-layout controller reports the storage failure to its caller. Product UI should preserve the user's unsaved chart workspace and expose a retry or recovery path instead of treating a failed request as a missing chart layout.
Low-level single-chart state
Use the chart-level API only when one chart is the entire persistence boundary and the host owns its save and load UI:
chart.getLayout(chartLayoutId)captures one chart asChartLayout.chart.applyLayout(chartLayout)applies a supplied single-chart snapshot.chart.loadLayout({ layoutId, apply: false })reads a stored chart layout without applying it.widget.chart().saveLayout()saves only the active chart, not the complete multi-chart workspace.
Do not use these methods as a substitute for widget.chartLayouts() when the
surface contains sibling charts or chart grid state.
Next steps
- Widget panel arrangements — define the separate widget panel state around charts.
- Drawing storage — choose whether drawings share the chart layout lifecycle.
- Backend integration — choose how chart layouts reach durable storage.
- Templates — apply reusable presets without changing chart layout identity.