Skip to content

Dashboards SDK API

Access the dashboards namespace via sdk.dashboards. A dashboard session (Dashboard instance) is opened with sdk.dashboards.open() or sdk.dashboards.view() and communicates over a live Y.js CRDT document.

The visibility field accepts private, workspace, and public. (unlisted is a retired legacy alias, still accepted on the API and treated as public.)

create(options: CreateOptions): Promise<CreateResult>
interface CreateOptions {
title: string;
description?: string;
template?: string;
layout?: 'fixed' | 'responsive';
columns?: number; // default 12
rowHeight?: number; // default 80 (px)
visibility?: 'private' | 'workspace' | 'public';
}
interface CreateResult {
id: string;
editorUrl: string; // shareout.site/a/{slug}
publishedUrl: string; // shareout.site/p/{slug}
editorArtifactId: string;
publishedArtifactId: string;
}
const result = await sdk.dashboards.create({
title: 'Sales Dashboard',
visibility: 'public',
});

Open a dashboard for editing. Requires authentication. Returns a connected Dashboard instance.

open(id: string): Promise<Dashboard>

Open a dashboard in published/view mode. No authentication required for public dashboards.

view(id: string): Promise<Dashboard>
list(): Promise<DashboardInfo[]>
interface DashboardInfo {
id: string;
title: string;
widgetCount: number;
editorUrl: string;
publishedUrl: string;
visibility: 'private' | 'workspace' | 'public';
createdAt: string;
updatedAt: string;
}
delete(id: string): Promise<boolean>

Returns false if not found, throws on other errors.


Returned by open() and view(). The connection is established automatically by both methods; you do not need to call connect() manually.

connect(): Promise<void> // re-connect after disconnect
disconnect(): void // disconnect but keep document
destroy(): void // full cleanup

Dashboard-level settings. Properties set here cascade to all widgets unless a widget specifies an override.

get(): DashboardMeta
interface DashboardMeta {
id: string;
title: string;
description: string;
// Layout
layout: 'fixed' | 'responsive';
columns: number;
rowHeight: number;
gap: number;
padding: number;
// Cascading visual defaults (inherited by widgets)
defaultFont: { heading: string; body: string; mono: string };
defaultColors: {
background: string;
surface: string;
text: string;
textSecondary: string;
accent: string;
positive: string;
negative: string;
neutral: string;
};
// Data defaults
refreshInterval: number | null; // auto-refresh in seconds
timezone: string;
dateFormat: string;
numberFormat: { locale: string; currency?: string };
createdBy: string;
updatedAt: string;
}
set(changes: Partial<DashboardMeta>): void
dashboard.meta.set({
title: 'Q4 Sales Dashboard',
defaultColors: {
background: '#0f172a',
surface: '#1e293b',
text: '#f8fafc',
accent: '#3b82f6',
},
refreshInterval: 300,
});
observe(handler: (meta: DashboardMeta) => void): () => void

Returns an unsubscribe function.


list(): Widget[]
interface Widget {
id: string;
type: WidgetType;
title: string;
dataSource: string | null;
query?: DataQuery;
config: WidgetConfig;
overrides?: WidgetOverrides;
owner: string | null;
locked: boolean;
}
type WidgetType = 'kpi' | 'chart' | 'table' | 'text' | 'image' | 'embed' | 'filter' | 'html';
interface DataQuery {
filter?: Record<string, any>;
sort?: { field: string; order: 'asc' | 'desc' };
limit?: number;
transform?: string; // JavaScript transform expression
}
interface WidgetOverrides {
surface?: string;
font?: { heading?: string; body?: string; mono?: string };
}
get(id: string): Widget | null
add(type: WidgetType, config: WidgetConfig, position?: LayoutItem): Widget
interface LayoutItem {
x: number; // column start (0-indexed)
y: number; // row start
w: number; // width in columns
h: number; // height in rows
minW?: number;
maxW?: number;
minH?: number;
maxH?: number;
}

Config shapes per type — see KPI config, Chart config, Table config below.

update(id: string, changes: Partial<Widget>): void
delete(id: string): boolean
duplicate(id: string): Widget
observe(handler: (widgets: Widget[]) => void): () => void

widgets.getContent() / widgets.setContent()

Section titled “widgets.getContent() / widgets.setContent()”

For html and text widgets. getContent() returns a Y.Text for collaborative binding; setContent() replaces content directly.

getContent(id: string): Y.Text
setContent(id: string, html: string): void

widgets.setOwner() / widgets.lock() / widgets.unlock()

Section titled “widgets.setOwner() / widgets.lock() / widgets.unlock()”
setOwner(id: string, userId: string | null): void
lock(id: string): void
unlock(id: string): void

Locked widgets can only be edited by their owner.


Grid position management.

get(): LayoutItem[]
update(widgetId: string, position: Partial<LayoutItem>): void
move(widgetId: string, x: number, y: number): void
resize(widgetId: string, w: number, h: number): void
observe(handler: (layout: LayoutItem[]) => void): () => void

type DataSourceType = 'static' | 'api' | 'sql' | 'shareout' | 'csv' | 'websocket';
interface DataSource {
id: string;
name: string;
type: DataSourceType;
config: DataSourceConfig;
refreshInterval?: number;
lastRefreshed?: string;
}
list(): DataSource[]
add(config: Omit<DataSource, 'id'>): DataSource
update(id: string, changes: Partial<DataSource>): void
delete(id: string): boolean
refresh(id: string): Promise<void>
refreshAll(): Promise<void>
getData(id: string): any[]
getFilteredData(id: string): any[]
observe(id: string, handler: (data: any[]) => void): () => void
// Static data
dashboard.dataSources.add({
name: 'Sales Data',
type: 'static',
config: { data: salesArray },
});
// REST API with auto-refresh
dashboard.dataSources.add({
name: 'Live Metrics',
type: 'api',
config: {
url: 'https://api.example.com/metrics',
method: 'GET',
headers: { Authorization: 'Bearer ...' },
},
refreshInterval: 60,
});
// ShareOut table
dashboard.dataSources.add({
name: 'Customers',
type: 'shareout',
config: { tableId: 'customers' },
});

interface FilterDefinition {
id: string;
type: 'select' | 'multiselect' | 'daterange' | 'numberrange' | 'search';
label: string;
dataSource?: string;
field?: string;
options?: { value: string; label: string }[];
defaultValue?: FilterValue;
affects: string[]; // widget IDs or '*' for all
}
type FilterValue =
| string
| string[]
| { from: string; to: string }
| { min: number; max: number };
getDefinitions(): FilterDefinition[]
addDefinition(def: Omit<FilterDefinition, 'id'>): FilterDefinition
updateDefinition(id: string, changes: Partial<FilterDefinition>): void
deleteDefinition(id: string): boolean
getState(): FilterState // { [filterId]: FilterValue }
setValue(filterId: string, value: FilterValue): void
reset(): void // reset all to defaults
observe(handler: (state: FilterState) => void): () => void

Saved filter combinations for quick access.

interface FilterPreset {
id: string;
name: string;
description?: string;
icon?: string;
color?: string;
filters: FilterState;
isDefault: boolean;
isPinned: boolean;
isShared: boolean;
createdBy: string;
createdAt: string;
}
list(): FilterPreset[]
create(preset: Omit<FilterPreset, 'id' | 'createdAt'>): FilterPreset
update(id: string, changes: Partial<FilterPreset>): void
delete(id: string): boolean
apply(id: string): void
setDefault(id: string | null): void
getDefault(): FilterPreset | null
pin(id: string): void
unpin(id: string): void
getPinned(): FilterPreset[]
observe(handler: (presets: FilterPreset[]) => void): () => void
dashboard.presets.create({
name: 'Q4 2026',
icon: '📊',
filters: dashboard.filters.getState(),
isPinned: true,
isShared: true,
isDefault: false,
});
dashboard.presets.apply('preset-q4-2026');
dashboard.presets.setDefault('preset-this-week');

Cross-widget interactions: clicking one widget filters others.

interface InteractionConfig {
id?: string;
trigger: {
widgetId: string;
event: 'click' | 'select' | 'hover';
field?: string;
};
action: {
type: 'filter' | 'navigate' | 'highlight' | 'custom';
target: string | string[]; // widget IDs or '*'
config: any;
};
}
define(config: InteractionConfig): string // returns interaction ID
remove(id: string): void
list(): InteractionConfig[]
trigger(widgetId: string, event: 'click' | 'select' | 'hover', data: Record<string, unknown>): void
onInteraction(handler: (event: InteractionEvent) => void): () => void
onWidgetInteraction(widgetId: string, handler: (event: InteractionEvent) => void): () => void
dashboard.interactions.define({
trigger: { widgetId: 'sales-chart', event: 'click', field: 'region' },
action: {
type: 'filter',
target: ['sales-table', 'kpi-revenue'],
config: { filterField: 'region' },
},
});

Presenter mode syncs focus, pointer, and cycling to all viewers in real time.

start(options?: PresenterOptions): Promise<void>
stop(): void
state(): DashboardPresentationState
isActive(): boolean
isPresenter(): boolean
// Widget focus
focusWidget(widgetId: string): void
clearFocus(): void
nextWidget(): void
previousWidget(): void
// Auto-cycling
startCycle(options?: CycleOptions): void
stopCycle(): void
// Timer
timer.elapsed(): number
timer.setCountdown(seconds: number): void
timer.remaining(): number | null
timer.pause(): void
timer.resume(): void
// Pointer
pointer.enable(): void
pointer.disable(): void
pointer.move(x: number, y: number): void
subscribe(handler: (state: DashboardPresentationState) => void): () => void
await dashboard.presenter.start({ countdown: 1800 }); // 30-min timer
dashboard.presenter.focusWidget('kpi-revenue');
dashboard.presenter.startCycle({ interval: 30, loop: true });

versions.list(): Promise<Version[]>
versions.create(name: string, description?: string): Promise<Version>
versions.restore(versionId: string): Promise<void>
versions.diff(fromId: string, toId: string): Promise<VersionDiff>
versions.delete(versionId: string): Promise<boolean>
versions.subscribe(handler: (versions: Version[]) => void): () => void
await dashboard.versions.create('Before meeting', 'Snapshot before live edits');
const versions = await dashboard.versions.list();
await dashboard.versions.restore(versions[2].id);

getUrl(): string // shareout.site/p/{slug}
setVisibility(v: 'private' | 'workspace' | 'public'): void
unpublish(): void
republish(): void

Ephemeral user presence over WebSocket (not persisted in the Y.js document).

set(state: Partial<DashboardPresenceState>): void
get(): Map<string, DashboardPresenceState>
subscribe(handler: (users: Map<string, DashboardPresenceState>) => void): () => void

Per-user undo stack scoped to the current user’s own changes.

undo.manager(): Y.UndoManager
undo.canUndo(): boolean
undo.canRedo(): boolean
undo.undo(): void
undo.redo(): void

Batch multiple changes into a single undo step.

transact(fn: () => void): void
dashboard.transact(() => {
const widget = dashboard.widgets.add('kpi', config, position);
dashboard.widgets.setOwner(widget.id, currentUserId);
});
// Both changes appear as one undo step

dashboard.on(event: DashboardEvent, handler: Function): void
dashboard.off(event: DashboardEvent, handler: Function): void
type DashboardEvent =
| 'widget:added' | 'widget:deleted' | 'widget:updated'
| 'layout:changed'
| 'filter:changed'
| 'data:refreshed'
| 'presentation:start' | 'presentation:end'
| 'sync' | 'status';

Formatting and data utilities.

helpers.formatNumber(value: number, options?: Intl.NumberFormatOptions): string
helpers.formatCurrency(value: number, currency?: string): string
helpers.formatPercent(value: number, decimals?: number): string
helpers.formatDate(date: Date | string, format?: string): string
helpers.getColorScale(type: 'sequential' | 'diverging' | 'categorical', name?: string): string[]
helpers.getSemanticColor(type: 'positive' | 'negative' | 'neutral' | 'warning'): string
helpers.aggregate(data: any[], groupBy: string, aggs: Aggregation[]): any[]
helpers.pivot(data: any[], rows: string, cols: string, values: string): any[]
helpers.timeSeries(data: any[], dateField: string, interval: 'day' | 'week' | 'month'): any[]

interface KPIConfig {
value: string;
label: string;
format: 'number' | 'currency' | 'percent' | 'custom';
formatOptions?: Intl.NumberFormatOptions;
comparison?: {
value: string;
type: 'absolute' | 'percent';
invertColors?: boolean;
};
sparkline?: { field: string; type: 'line' | 'bar' | 'area' };
icon?: string;
size?: 'sm' | 'md' | 'lg';
}
interface ChartConfig {
chartType: 'line' | 'bar' | 'area' | 'pie' | 'donut' | 'scatter' | 'heatmap' | 'gauge' | 'funnel' | 'treemap';
xAxis?: { field: string; type: 'category' | 'time' | 'value'; label?: string };
yAxis?: { field: string; label?: string; min?: number; max?: number };
series?: { field: string; name?: string; color?: string }[];
nameField?: string;
valueField?: string;
legend?: { show: boolean; position: 'top' | 'bottom' | 'left' | 'right' };
tooltip?: { show: boolean };
animation?: boolean;
}
interface TableConfig {
columns: TableColumn[];
pageSize?: number;
sortable?: boolean;
filterable?: boolean;
exportable?: boolean;
selectable?: boolean;
condensed?: boolean;
striped?: boolean;
}
interface TableColumn {
field: string;
header: string;
width?: string;
align?: 'left' | 'center' | 'right';
format?: 'text' | 'number' | 'currency' | 'percent' | 'date' | 'boolean' | 'link' | 'image' | 'badge';
sortable?: boolean;
filterable?: boolean;
}
interface TextConfig {
content: string;
contentType: 'markdown' | 'html';
align?: 'left' | 'center' | 'right';
padding?: number;
}
interface HTMLConfig {
content: string;
scripts?: boolean; // default false
}

The Y.js document backing each dashboard has the following top-level maps:

MapTypeContents
metaY.MapDashboard metadata and cascading visual properties
widgetsY.Map<string, Widget>Widget definitions keyed by widget ID
widgetContentY.Map<string, Y.Text>Collaborative HTML/text content for html and text widgets
layoutY.Map<string, LayoutItem>Grid positions keyed by widget ID
dataSourcesY.Map<string, DataSource>Data connection configs
filtersY.Map<string, FilterValue>Current filter state
filterDefsY.Array<FilterDefinition>Filter definitions
filterPresetsY.Map<string, FilterPreset>Saved filter combinations
interactionsY.Map<string, InteractionConfig>Cross-widget interaction configs
presentationStateY.MapLive presenter state (synced to all viewers)

Presence state is ephemeral — it is not in the Y.js document; it travels over the WebSocket only.

Version snapshots are stored separately as Y.js encoded state vectors in the dashboard_versions table, not embedded in the live document.