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.parent is 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.

The following shows this logic in React:
JAVASCRIPT
import { VisualizationAPI } from '@splunk/dashboard-extension'; 
 
VisualizationAPI.addDataSourcesListener(({ dataSources, loading }) => { 
    if (loading) return; 
    const data = dataSources?.primary?.data; 
    if (!data) return; 
    render(data); 
});
The following shows this logic in JavaScript:
JAVASCRIPT
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

Search results are delivered in a columnar format via dataSources.primary.data. Visualizations receive data in the following format:
JSON
{ 
    fields: [{ name: 'host' }, { name: 'count' }], 
    columns: [ 
        ['host-1', 'host-2'],   // values for 'host' 
        ['42',     '17']        // values for 'count' 
    ] 
}
The data is organized into the following parts:
  • fields: list of objects that define the headers for your data. Each object contains a name property, 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 as columns[i]) contains all the values for the corresponding field at fields[i].
    • To access a specific piece of information, use columns[i][j], where i is the column index and j is the row index.
To convert this data to a row-oriented format, see the following:
JAVASCRIPT
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' }
Note: All values are strings. Apply 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:
JSON
"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:
JSON
"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" 
                } 
            ] 
        ] 
    } 
]
The following shows how to read options in React:
JAVASCRIPT
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} />; 
}
The following shows how to read options in JavaScript:
JAVASCRIPT
VisualizationAPI.addOptionsListener(({ options }) => { 
    const color = options.chartColor ?? '#4e9cf5'; 
    render({ color }); 
});
Note: Always provide fallback values when reading options. Options may be unset if the dashboard author has not configured them, regardless of the 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:

  • setOptions is 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.

The following shows this conditioning in React:
JAVASCRIPT
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> 
    ); 
}
The following shows this conditioning in JavaScript:
JAVASCRIPT
VisualizationAPI.addModeListener(({ mode }) => { 
    const editControls = document.getElementById('edit-controls'); 
    editControls.style.display = mode === 'edit' ? 'block' : 'none'; 
});

Drilldown

You must activate drilldown in config.json before use:
JSON
{ 
    "showDrilldown": true, 
    "hasEventHandlers": true 
}
You can use one of the following methods to use drilldown with your custom visualization.
Method Description Code sample
Element-based drilldown addDrilldownListener registers a DOM node as a drilldown target. Dashboard Studio manages the interaction.
PYTHON
import { VisualizationAPI } from '@splunk/dashboard-extension'; 
 
VisualizationAPI.addDrilldownListener({ 
    node: document.getElementById('my_bar'), 
    action: 'linkTo', 
    payloadCallback: (event) => ({ 
        action: 'linkTo', 
        payload: { value: event.target.dataset.value }, 
    }), 
});
Programmatic drilldown triggerDrilldown triggers a drilldown from custom event handling logic. Assumes the drilldown originates from a click event.
JSON
element.addEventListener('click', () => { 
    VisualizationAPI.triggerDrilldown({ 
        action: 'setToken', 
        payload: { name: 'selected_host', value: 'host-1' }, 
    }); 
});
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
VisualizationAPI.triggerDrilldown({ 
    action: 'setToken', 
    payload: { name: 'selected_host', value: 'host-1' }, 
});

Tokens

Tokens are dashboard-level variables shared across visualizations and inputs. A visualization can read current token values and, if configured, set them in response to user interaction. You can enable token support on a visualization via config.json:
JSON
{ 
    "canSetTokens": ["dynamic", "static"] 
}
  • "dynamic": tokens are set at runtime

  • "static": tokens are set at configuration time

The following shows how to read tokens in React:
JAVASCRIPT
import { useTokens } from '@splunk/dashboard-extension/react'; 
 
function MyVisualization() { 
    const { tokens } = useTokens(); 
    const selectedHost = tokens?.selected_host; 
    return <div>Selected: {selectedHost ?? 'none'}</div>; 
}
The following shows how to read tokens in JavaScript:
JAVASCRIPT
import { VisualizationAPI } from '@splunk/dashboard-extension'; 
 
VisualizationAPI.addTokensListener(({ tokens }) => { 
    const selectedHost = tokens?.selected_host; 
    updateDisplay(selectedHost); 
});
You configure setting a token via the setToken drilldown action using triggerDrilldown or triggerDrilldown:
JSON
VisualizationAPI.triggerDrilldown({ 
    action: 'setToken', 
    payload: { name: 'selected_host', value: 'host-1' }, 
});
Note: Setting a token updates the value dashboard-wide. All visualizations and inputs on the dashboard that reference the token update in response.