Settings: survive the reload
Save your extension’s configuration into the workbook itself, so it wakes up configured — for every viewer, on every open.
Why it matters
Without settings, every load is a first date: your extension wakes up not knowing which worksheet to watch or what threshold to alert on. The settings API writes key-value strings into the workbook — saved with it, published with it, downloaded with it. The author configures once; every viewer gets the configured extension. Values are strings only, so JSON.stringify is the idiom for anything structured. This is the feature that separates a demo from a product: a configurable extension is one .trex reused across fifty dashboards, not fifty forks of the code.
Write the JavaScript for a Tableau dashboard extension with a minimal settings flow: on initialize, read tableau.extensions.settings.get('config'), JSON.parse it with a fallback to { worksheet: null, threshold: 0.1 }, and render the current values. Provide a form — a worksheet dropdown populated from dashboard.worksheets and a numeric threshold input — that on submit calls settings.set('config', JSON.stringify(newConfig)) followed by await settings.saveAsync(), wrapped in try/catch with failures reported to the user, since saving requires edit rights on the workbook. Also subscribe to tableau.TableauEventType.SettingsChanged and re-render, so two open instances stay in sync.
Four calls and one event cover it. settings.set(key, value) stages a string; saveAsync() commits every staged change into the workbook — that is the only call that persists anything, and it needs the workbook to be editable, so save from authoring mode and treat a failure as something to tell the user, not swallow. get(key) reads at any time, including immediately after initializeAsync() on the next load. SettingsChanged fires everywhere the workbook is open, which keeps a second instance of your extension honest.
The JSON round-trip below is the standard shape: one config key, structured object inside, parse-with-fallback on the way out.
const DEFAULTS = { worksheet: null, threshold: 0.1 };
function loadConfig() {
try {
return { ...DEFAULTS,
...JSON.parse(tableau.extensions.settings.get('config')) };
} catch (e) {
return { ...DEFAULTS }; // first run, or malformed — start clean
}
}
async function saveConfig(config) {
tableau.extensions.settings.set('config', JSON.stringify(config));
try {
await tableau.extensions.settings.saveAsync();
console.log('saved:', config);
} catch (e) {
// Read-only context (viewing, not authoring) — tell the user.
console.error('could not save — open the workbook for editing', e);
}
}
(async () => {
await tableau.extensions.initializeAsync();
let config = loadConfig();
console.log('woke up with', config);
// Another instance saved? Stay current.
tableau.extensions.addEventListener(
tableau.TableauEventType.SettingsChanged,
() => { config = loadConfig(); console.log('reloaded', config); });
// Example: persist the first worksheet as the watched target.
const ws = tableau.extensions.dashboardContent.dashboard.worksheets[0];
await saveConfig({ ...config, worksheet: ws.name });
})();Got what you came for? Mark the stop and the line fills in beneath you.