docs/plugins/development/config.md
Plugins can expose user-configurable settings that appear in the editor's Settings UI under their own "Plugin: <name>" category. The values are saved alongside the rest of the user config and respect the same User/Project/Session layering as built-in settings.
Each setting is declared from TypeScript by calling one of the
strongly-typed editor.defineConfigX(...) methods. The host validates
the call synchronously — a typo or wrong shape throws an exception
right there, at the call site, instead of failing silently later. The
return type is inferred from the call, so plugin code is type-safe
without any cast.
const autoEnable = editor.defineConfigBoolean("autoEnable", {
default: false,
description: "Auto-enable the plugin on startup",
});
const maxItems = editor.defineConfigInteger("maxItems", {
default: 5,
minimum: 1,
maximum: 50,
});
const mode = editor.defineConfigEnum("mode", {
values: ["normal", "insert"] as const,
default: "normal",
});
const patterns = editor.defineConfigStringArray("patterns", {
default: ["TODO", "FIXME"],
});
// `mode` is typed as `"normal" | "insert"` thanks to the `as const`.
// `autoEnable: boolean`, `maxItems: number`, `patterns: string[]`.
Each call returns the current merged value (user override if any,
otherwise the declared default). For values that may change while the
plugin is running, re-read on demand:
const cfg = editor.getPluginConfig() as { autoEnable?: boolean };
if (cfg.autoEnable) { /* ... */ }
The value a defineConfigX(...) call returns is a snapshot from plugin
load time. If you keep it in a variable instead of re-reading it at the
point of use, subscribe to config_changed — otherwise your setting
will appear to do nothing until the editor restarts:
let autoEnable = editor.defineConfigBoolean("autoEnable", { default: false });
editor.on("config_changed", () => {
const cfg = (editor.getPluginConfig() ?? {}) as { autoEnable?: boolean };
autoEnable = cfg.autoEnable === true;
});
The hook fires after the user saves from the Settings UI and after a
config reload from disk, once the new config is live — so
getPluginConfig() and getConfig() already return the new values when
your handler runs. It carries no payload: re-read the keys you care
about and compare against what you were holding.
It does not fire for your own editor.setSetting(...) writes, so a
plugin that re-configures itself from this handler won't wake itself
back up.
Plugins that already read the setting at point of use (as in the
getPluginConfig() example above) need no subscription — they pick up
the change on the next read.
| Method | TS return type | Settings UI widget |
|---|---|---|
defineConfigBoolean | boolean | Toggle |
defineConfigInteger | number | Number input |
defineConfigNumber | number | Number input |
defineConfigString | string | Text input |
defineConfigEnum | one of values | Dropdown |
defineConfigStringArray | string[] | List editor |
All methods accept description (string). Numeric methods accept
minimum / maximum. Each method's options parameter is strictly
typed — unknown keys (defualt → "Did you mean 'default'?"), wrong
default types, and out-of-range defaults are all compile-time errors.
The Settings UI renders one Plugin: <name> top-level category per enabled plugin that has registered at least one field, sorted alongside the built-in categories.
By design:
"ui.status_bar_fg", "editor.diff_add_bg") to
addOverlay / addVirtualLine instead, so the colors track the
user's active theme.x-enum-from pointing at host config paths like /languages.
Plugins that need to react to host config should read
editor.getConfig() directly at use-time.Plugin settings follow the same layered-config rules as the rest of the editor:
plugins.<name>.settings entry
in the user's config (cheap; harmless). Reinstalling restores the
values.defineConfigX(...) calls, so
plugin authors iterating with Reload Plugin see updated schemas
immediately.