Settings System (defineSettings)
dmn.plugin.defineSettings is a versatile settings management API for defining settings independent of panels.
When to Use?
| Use Case | Description |
|---|---|
| Global settings for multiple panels | Common settings shared by multiple defineElement panels |
| Independent feature settings | Settings for features without panels like notifications, shortcuts, API integrations |
| Plugin environment settings | Options affecting the entire plugin |
Basic Usage
// @id my-plugin
const pluginSettings = dmn.plugin.defineSettings({
settings: {
connectionSection: {
type: "section",
label: "Connection",
},
apiKey: {
type: "string",
default: "",
label: "API Key",
},
theme: {
type: "select",
options: [
{ value: "dark", label: "Dark" },
{ value: "light", label: "Light" },
],
default: "dark",
label: "Theme",
},
behaviorSection: {
type: "section",
label: "Behavior",
},
enabled: {
type: "boolean",
default: true,
label: "Enabled",
},
// Conditional visibility: static boolean or function, shown only while enabled is true
advancedOption: {
type: "number",
default: 5,
label: "Advanced Option",
visible: (settings) => settings.enabled,
},
},
});
// section starts a new card. (The legacy divider type was removed, so existing divider entries are ignored.)
// Get settings values
const current = pluginSettings.get();
console.log("API Key:", current.apiKey);
// Change settings values
try {
await pluginSettings.set({ theme: "light" });
} catch (error) {
console.error("Failed to save settings:", error);
}
// Open settings panel (default: property panel)
const confirmed = await pluginSettings.open();
if (confirmed) {
console.log("Settings saved!");
}defineSettings uses the same section contract as defineElement: a section starts
a new card, its optional label appears above the card, and its key is excluded from
stored/default values and callbacks. section.visible controls the entire group.
Sections with no renderable value settings are hidden entirely; only when no value
setting is renderable anywhere does the settings UI show its standard empty state.
Panel and modal modes have identical semantics, including fail-closed visibility
evaluation.
API Reference
defineSettings(definition)
interface PluginSettingsDefinition {
// Settings schema (same format as defineElement)
settings: Record<string, PluginSettingSchema>;
// i18n messages (optional)
messages?: Record<string, Record<string, string>>;
// Settings UI mode (optional, default: "panel")
settingsUI?: "panel" | "modal";
// Callback on settings change (optional)
onChange?: (newSettings, oldSettings) => void;
}Return Value (PluginSettingsInstance)
| Method | Description |
|---|---|
get() | Get current settings |
set(updates) | Change settings (auto-save) |
open() | Open settings panel (default: property panel) |
reset() | Reset to defaults |
subscribe(listener) | Subscribe to settings changes |
set() and reset() reject their promises if saving fails. The failed edit is
restored from saved settings without overwriting a newer edit started during
recovery. Change callbacks and subscriber notifications run only after a successful
save. A property panel save failure shows an error and resolves open() to false;
a modal save failure rejects open().
onChange vs subscribe()
Both detect settings changes but have different purposes:
onChange | subscribe() | |
|---|---|---|
| Declaration location | Inside defineSettings() definition | Anywhere |
| Can unsubscribe | ❌ No | ✅ Yes |
| Count | 1 only | Multiple |
| Purpose | Core/essential logic | Conditional/temporary logic |
const settings = dmn.plugin.defineSettings({
settings: { apiKey: { type: "string", default: "", label: "API Key" } },
// onChange: Always executes (cannot unsubscribe)
onChange: (newSettings, oldSettings) => {
if (newSettings.apiKey !== oldSettings.apiKey) {
reconnectAPI(newSettings.apiKey);
}
},
});
// subscribe: Subscribe only when needed, unsubscribe later
function openPreviewPanel() {
const panel = createPanel();
const unsubscribe = settings.subscribe((newSettings) => {
panel.update(newSettings);
});
panel.onClose = () => unsubscribe();
}i18n Support
const pluginSettings = dmn.plugin.defineSettings({
settings: {
volume: {
type: "number",
default: 50,
min: 0,
max: 100,
label: "settings.volume", // Message key
},
},
messages: {
ko: {
"settings.volume": "볼륨",
},
en: {
"settings.volume": "Volume",
},
},
});Practical Examples
Global Settings + defineElement Integration
// @id kps-plugin
// Define global settings
const globalSettings = dmn.plugin.defineSettings({
settings: {
defaultColor: {
type: "color",
default: "#86EFAC",
label: "Default Color",
},
refreshRate: {
type: "number",
default: 50,
min: 10,
max: 200,
label: "Refresh Rate (ms)",
},
},
});
// Define panel
dmn.plugin.defineElement({
name: "KPS Panel",
contextMenu: {
create: "Create KPS Panel",
delete: "Delete KPS Panel",
items: [
{
label: "Global Settings",
onClick: () => globalSettings.open(),
},
],
},
// Instance-specific settings
settings: {
showGraph: { type: "boolean", default: true, label: "Show Graph" },
graphColor: {
type: "color",
default: "#86EFAC",
label: "Graph Color",
visible: (s) => s.showGraph,
},
},
template: (state, instanceSettings, { html }) => {
const global = globalSettings.get();
return html`
<div style="color: ${global.defaultColor};">KPS: ${state.kps ?? 0}</div>
`;
},
onMount: ({ setState, onHook }) => {
const global = globalSettings.get();
let count = 0;
onHook("key", ({ state }) => {
if (state === "DOWN") count++;
});
const interval = setInterval(() => {
setState({ kps: count });
count = 0;
}, global.refreshRate);
return () => clearInterval(interval);
},
});Adding Settings to the Grid Menu
You can also add a settings menu without any panel:
// @id settings-only-plugin
const pluginSettings = dmn.plugin.defineSettings({
settings: {
volume: { type: "number", default: 50, min: 0, max: 100, label: "Volume" },
notifications: { type: "boolean", default: true, label: "Notifications" },
},
});
// Add to the right-click menu on empty grid space
dmn.ui.contextMenu.addGridMenuItem({
id: "my-plugin-settings",
label: "Plugin Settings",
onClick: () => pluginSettings.open(),
});
dmn.plugin.registerCleanup(() => {
dmn.ui.contextMenu.clearMyMenuItems();
});Reacting to Settings Changes
// @id data-fetcher
const fetcherSettings = dmn.plugin.defineSettings({
settings: {
apiEndpoint: {
type: "string",
default: "https://api.example.com",
label: "API Endpoint",
},
refreshInterval: {
type: "number",
default: 5000,
min: 1000,
max: 60000,
label: "Refresh Interval (ms)",
},
autoRefresh: {
type: "boolean",
default: true,
label: "Auto Refresh",
},
},
});
let fetchInterval = null;
function startFetching() {
const { refreshInterval, autoRefresh, apiEndpoint } = fetcherSettings.get();
if (fetchInterval) {
clearInterval(fetchInterval);
fetchInterval = null;
}
if (!autoRefresh) return;
fetchInterval = setInterval(async () => {
const response = await fetch(apiEndpoint);
const data = await response.json();
console.log("Data:", data);
}, refreshInterval);
}
// Restart the interval when settings change
fetcherSettings.subscribe((newSettings, oldSettings) => {
if (
newSettings.apiEndpoint !== oldSettings.apiEndpoint ||
newSettings.refreshInterval !== oldSettings.refreshInterval ||
newSettings.autoRefresh !== oldSettings.autoRefresh
) {
startFetching();
}
});
startFetching();
dmn.plugin.registerCleanup(() => {
if (fetchInterval) clearInterval(fetchInterval);
});Comparison with defineElement’s onSettingsChange
| Feature | defineElement | defineSettings |
|---|---|---|
| Settings change handling | onSettingsChange(callback) | onChange + subscribe() |
| Unsubscribing | Automatic (on unmount) | Manual, via subscribe() return value |
| Where to use | Inside onMount only | Anywhere |
| Target | Instance-specific settings | Global/standalone settings |
Automatic Behavior
| Feature | Description |
|---|---|
| Automatic UI generation | Property panel/modal generated from the settings schema |
| Automatic storage handling | Saved to and restored from plugin.storage |
| i18n support | Integrates with messages |
| Per-type components | boolean→checkbox, color→color picker, and so on |
| Automatic panel sync | All panels of the same plugin re-render when settings change |