Best practices
Learn how to build robust Dashboard Studio custom visualizations that handle iframe isolation, asynchronous data, configuration options, modes, drilldown, and tokens.
Use these best practices to ensure your Dashboard Studio custom visualizations are robust, maintainable, and consistent with the rest of the dashboard experience.
Iframe architecture
Dashboard Studio custom visualizations run inside a sandboxed iframe that is isolated from the parent Dashboard Studio frame. All communication between the visualization and Dashboard Studio occurs via postMessage. The @splunk/dashboard-extension API and React hooks handle this messaging internally.
Due to this architecture, you must consider the following implications of the iframe boundary:
-
window.parentis not accessible from within the visualization. -
The DOM of the Dashboard Studio page cannot be read or modified directly.
-
State (data, options, theme, tokens) is delivered asynchronously. It is not available synchronously at startup.
The initial state is delivered via the first listener callback or hook render. Visualizations should handle the case where state has not yet arrived.
Loading state
Data from a search is not immediately available when the visualization loads. The loading flag indicates that a search is in progress. Rendering before data arrives results in errors or a blank visualization. Therefore, the loading flag and a null check on dataSources?.primary?.data should gate all rendering logic.
import { VisualizationAPI } from '@splunk/dashboard-extension';
VisualizationAPI.addDataSourcesListener(({ dataSources, loading }) => {
if (loading) return;
const data = dataSources?.primary?.data;
if (!data) return;
render(data);
});
import { VisualizationAPI } from '@splunk/dashboard-extension';
VisualizationAPI.addDataSourcesListener(({ dataSources, loading }) => {
if (loading) return;
const data = dataSources?.primary?.data;
if (!data) return;
render(data);
});
Data formatting
dataSources.primary.data. Visualizations receive data in the following format:
{
fields: [{ name: 'host' }, { name: 'count' }],
columns: [
['host-1', 'host-2'], // values for 'host'
['42', '17'] // values for 'count'
]
}
fields: list of objects that define the headers for your data. Each object contains anameproperty, which acts as the label for that column.columns: list containing the actual data, organized by column rather than by row.- Each item in
columns(represented ascolumns[i]) contains all the values for the corresponding field atfields[i]. - To access a specific piece of information, use
columns[i][j], whereiis the column index andjis the row index.
- Each item in
const { fields, columns } = dataSources.primary.data;
const fieldNames = fields.map(f => f.name);
const rows = columns[0].map((_, rowIndex) =>
Object.fromEntries(fieldNames.map((name, colIndex) => [name, columns[colIndex][rowIndex]]))
);
// rows[0] → { host: 'host-1', count: '42' }
parseInt, parseFloat, or equivalent parsing for numeric fields.
Options, optionSchema, and editorConfig
optionsSchema and editorConfig in config.json define the configurable properties of the visualization and how they are presented in the Dashboard Studio editor panel.
optionsSchema defines the structure and default values of the visualization's options using JSON Schema:
"optionsSchema": {
"chartColor": {
"type": "string",
"default": "#4e9cf5"
},
"showLegend": {
"type": "boolean",
"default": true
},
"maxItems": {
"type": "number",
"default": 10
}
}
editorConfig defines the editor UI rendered in the Studio panel. Each entry in the layout maps an editor component to an option from the schema:
"editorConfig": [
{
"label": "Display",
"layout": [
[
{
"label": "Chart Color",
"editor": "editor.color",
"option": "chartColor"
}
],
[
{
"label": "Show Legend",
"editor": "editor.checkbox",
"option": "showLegend"
},
{
"label": "Max Items",
"editor": "editor.number",
"option": "maxItems"
}
]
]
}
]
import { useOptions } from '@splunk/dashboard-extension/react';
function MyVisualization() {
const { options } = useOptions();
const color = options.chartColor ?? '#4e9cf5';
const showLegend = options.showLegend ?? true;
return <MyChart color={color} showLegend={showLegend} />;
}
VisualizationAPI.addOptionsListener(({ options }) => {
const color = options.chartColor ?? '#4e9cf5';
render({ color });
});
default specified in optionsSchema.
View and Edit mode
Dashboard Studio operates in the following modes:
-
View: when a user views the dashboard
-
Edit: when a user edits the dashboard
The modes have the following differences:
-
setOptionsis only accepted in edit mode. Calls made in view mode are silently ignored. -
The editor panel, driven by
editorConfig, is only visible in edit mode.
Use useMode() in React or addModeListener in JavaScript to conditionally render affordances that only apply during editing.
import { useMode, useOptions } from '@splunk/dashboard-extension/react';
function MyVisualization() {
const { mode } = useMode();
const { options, setOptions } = useOptions();
return (
<div>
{mode === 'edit' && (
<button onClick={() => setOptions({ ...options, showLegend: true })}>
Reset Legend
</button>
)}
<MyChart options={options} />
</div>
);
}
VisualizationAPI.addModeListener(({ mode }) => {
const editControls = document.getElementById('edit-controls');
editControls.style.display = mode === 'edit' ? 'block' : 'none';
});
Drilldown
{
"showDrilldown": true,
"hasEventHandlers": true
}
| Method | Description | Code sample |
|---|---|---|
| Element-based drilldown | addDrilldownListener registers a DOM node as a drilldown target. Dashboard Studio manages the interaction. |
PYTHON
|
| Programmatic drilldown | triggerDrilldown triggers a drilldown from custom event handling logic. Assumes the drilldown originates from a click event. |
JSON
|
triggerDrilldown is available when the drilldown originates from a non-click event, such as a hover, keyboard event, or programmatic trigger. It makes no assumptions about the originating interaction. |
JSON
|
Tokens
{
"canSetTokens": ["dynamic", "static"]
}
-
"dynamic": tokens are set at runtime -
"static": tokens are set at configuration time
import { useTokens } from '@splunk/dashboard-extension/react';
function MyVisualization() {
const { tokens } = useTokens();
const selectedHost = tokens?.selected_host;
return <div>Selected: {selectedHost ?? 'none'}</div>;
}
import { VisualizationAPI } from '@splunk/dashboard-extension';
VisualizationAPI.addTokensListener(({ tokens }) => {
const selectedHost = tokens?.selected_host;
updateDisplay(selectedHost);
});
setToken drilldown action using triggerDrilldown or triggerDrilldown:
VisualizationAPI.triggerDrilldown({
action: 'setToken',
payload: { name: 'selected_host', value: 'host-1' },
});