15 KiB
Plugin Settings UI Guide
This guide explains how to structure and format plugin settings UI in Obsidian, with examples for building multiple plugins with distinct features.
Table of Contents
Core Concepts
Settings Interface & Settings Tab
Every plugin has:
- Settings Interface - TypeScript interface defining your data structure
- Settings Tab Class - Extends
PluginSettingTaband defines the UI
// Define the data structure
export interface MyPluginSettings {
settingName: string;
settingValue: boolean;
}
// Default values
export const DEFAULT_SETTINGS: MyPluginSettings = {
settingName: 'default',
settingValue: false,
};
// UI definition
export class MySettingTab extends PluginSettingTab {
plugin: MyPlugin;
constructor(app: App, plugin: MyPlugin) {
super(app, plugin);
this.plugin = plugin;
}
display(): void {
const { containerEl } = this;
containerEl.empty(); // Clear previous UI
// Add UI elements here
}
}
Container Structure
The containerEl is where you build your UI. Always start with containerEl.empty() to clear old content when the settings tab reopens.
display(): void {
const { containerEl } = this;
containerEl.empty();
// Add heading
containerEl.createEl('h2', { text: 'Plugin Name' });
// Add settings
new Setting(containerEl)
.setName('Setting Name')
.setDesc('Description of what this does')
.addText((text) => { /* ... */ });
}
Setting Types
Text Input
Single-line text input:
new Setting(containerEl)
.setName('Plugin Name')
.setDesc('The name of your plugin')
.addText((text) =>
text
.setPlaceholder('Enter name')
.setValue(this.plugin.settings.pluginName)
.onChange(async (value) => {
this.plugin.settings.pluginName = value;
await this.plugin.saveSettings();
})
);
Text Area
Multi-line text input:
new Setting(containerEl)
.setName('Configuration')
.setDesc('Multi-line configuration')
.addTextArea((text) =>
text
.setPlaceholder('Enter configuration')
.setValue(this.plugin.settings.config)
.onChange(async (value) => {
this.plugin.settings.config = value;
await this.plugin.saveSettings();
})
);
Toggle
Boolean switch:
new Setting(containerEl)
.setName('Enable Feature')
.setDesc('Turn this feature on or off')
.addToggle((toggle) =>
toggle
.setValue(this.plugin.settings.enabled)
.onChange(async (value) => {
this.plugin.settings.enabled = value;
await this.plugin.saveSettings();
})
);
Dropdown
Select from predefined options:
new Setting(containerEl)
.setName('Theme')
.setDesc('Choose a color theme')
.addDropdown((dropdown) =>
dropdown
.addOption('light', 'Light')
.addOption('dark', 'Dark')
.addOption('auto', 'Auto')
.setValue(this.plugin.settings.theme)
.onChange(async (value) => {
this.plugin.settings.theme = value;
await this.plugin.saveSettings();
})
);
Slider
Numeric input with range:
new Setting(containerEl)
.setName('Search Depth')
.setDesc('How deep to search in folders (1-10)')
.addSlider((slider) =>
slider
.setMin(1)
.setMax(10)
.setStep(1)
.setValue(this.plugin.settings.searchDepth)
.onChange(async (value) => {
this.plugin.settings.searchDepth = value;
await this.plugin.saveSettings();
})
);
Button
Trigger an action:
new Setting(containerEl)
.setName('Process Files')
.setDesc('Start processing all files')
.addButton((button) =>
button
.setButtonText('Process Now')
.onClick(async () => {
await this.plugin.processAllFiles();
new Notice('Processing complete!');
})
);
Color Picker
Select a color:
new Setting(containerEl)
.setName('Tag Color')
.setDesc('Color for tags')
.addColorPicker((color) =>
color
.setValue(this.plugin.settings.tagColor)
.onChange(async (value) => {
this.plugin.settings.tagColor = value;
await this.plugin.saveSettings();
})
);
UI Organization Patterns
Section Headers
Organize settings into logical sections:
display(): void {
const { containerEl } = this;
containerEl.empty();
// Main heading
containerEl.createEl('h2', { text: 'Plugin Settings' });
// Section 1
containerEl.createEl('h3', { text: 'Basic Configuration' });
new Setting(containerEl).setName('Setting 1')...
new Setting(containerEl).setName('Setting 2')...
// Section 2
containerEl.createEl('h3', { text: 'Advanced Options' });
new Setting(containerEl).setName('Setting 3')...
new Setting(containerEl).setName('Setting 4')...
}
Conditional Display
Show/hide settings based on other settings:
display(): void {
const { containerEl } = this;
containerEl.empty();
new Setting(containerEl)
.setName('Enable Advanced')
.addToggle((toggle) =>
toggle
.setValue(this.plugin.settings.advancedMode)
.onChange(async (value) => {
this.plugin.settings.advancedMode = value;
await this.plugin.saveSettings();
this.display(); // Redraw to show/hide advanced settings
})
);
if (this.plugin.settings.advancedMode) {
new Setting(containerEl)
.setName('Advanced Option')
.setDesc('This only shows when advanced mode is on')
.addText((text) => { /* ... */ });
}
}
Grouped Controls
Multiple controls in one setting:
new Setting(containerEl)
.setName('Color')
.setDesc('Choose color and opacity')
.addColorPicker((color) =>
color
.setValue(this.plugin.settings.color)
.onChange(async (value) => {
this.plugin.settings.color = value;
await this.plugin.saveSettings();
})
)
.addSlider((slider) =>
slider
.setMin(0)
.setMax(100)
.setValue(this.plugin.settings.opacity)
.onChange(async (value) => {
this.plugin.settings.opacity = value;
await this.plugin.saveSettings();
})
);
Multi-Plugin Architecture
When building multiple plugins within one codebase, consider this structure:
Folder Structure
src/
main.ts (Plugin entry point)
settings.ts (Shared settings)
plugins/
folder/
folderPlugin.ts
folderSettings.ts
markdown/
markdownPlugin.ts
markdownSettings.ts
kanban/
kanbanPlugin.ts
kanbanSettings.ts
Unified Settings Interface
// settings.ts
export interface PluginConfig {
// Folder Plugin settings
folderPlugin: {
enabled: boolean;
organizationHierarchy: string;
};
// Markdown Plugin settings
markdownPlugin: {
enabled: boolean;
autoCreatePath: string;
template: string;
};
// Kanban Plugin settings
kanbanPlugin: {
enabled: boolean;
boardTemplate: string;
defaultLayout: string;
};
}
export const DEFAULT_SETTINGS: PluginConfig = {
folderPlugin: {
enabled: true,
organizationHierarchy: 'Category, Status',
},
markdownPlugin: {
enabled: true,
autoCreatePath: 'Notes',
template: 'default',
},
kanbanPlugin: {
enabled: false,
boardTemplate: 'default',
defaultLayout: 'columns',
},
};
Unified Settings Tab
// settings.ts
export class UnifiedSettingTab extends PluginSettingTab {
plugin: MultiPluginBase;
display(): void {
const { containerEl } = this;
containerEl.empty();
containerEl.createEl('h2', { text: 'Plugin Suite Settings' });
// Folder Plugin Section
this.displayFolderPluginSettings(containerEl);
// Markdown Plugin Section
this.displayMarkdownPluginSettings(containerEl);
// Kanban Plugin Section
this.displayKanbanPluginSettings(containerEl);
}
displayFolderPluginSettings(containerEl: HTMLElement): void {
containerEl.createEl('h3', { text: 'Folder Organization Plugin' });
new Setting(containerEl)
.setName('Enable Folder Plugin')
.addToggle((toggle) =>
toggle
.setValue(this.plugin.settings.folderPlugin.enabled)
.onChange(async (value) => {
this.plugin.settings.folderPlugin.enabled = value;
await this.plugin.saveSettings();
})
);
if (this.plugin.settings.folderPlugin.enabled) {
new Setting(containerEl)
.setName('Organization Hierarchy')
.setDesc('Comma-separated frontmatter fields')
.addTextArea((text) =>
text
.setValue(this.plugin.settings.folderPlugin.organizationHierarchy)
.onChange(async (value) => {
this.plugin.settings.folderPlugin.organizationHierarchy = value;
await this.plugin.saveSettings();
})
);
}
}
displayMarkdownPluginSettings(containerEl: HTMLElement): void {
containerEl.createEl('h3', { text: 'Markdown Auto-Create Plugin' });
new Setting(containerEl)
.setName('Enable Markdown Plugin')
.addToggle((toggle) =>
toggle
.setValue(this.plugin.settings.markdownPlugin.enabled)
.onChange(async (value) => {
this.plugin.settings.markdownPlugin.enabled = value;
await this.plugin.saveSettings();
})
);
if (this.plugin.settings.markdownPlugin.enabled) {
new Setting(containerEl)
.setName('Auto-Create Path')
.setDesc('Default folder for auto-created files')
.addText((text) =>
text
.setPlaceholder('Notes')
.setValue(this.plugin.settings.markdownPlugin.autoCreatePath)
.onChange(async (value) => {
this.plugin.settings.markdownPlugin.autoCreatePath = value;
await this.plugin.saveSettings();
})
);
new Setting(containerEl)
.setName('Template')
.setDesc('Template to use for new files')
.addDropdown((dropdown) =>
dropdown
.addOption('default', 'Default')
.addOption('minimal', 'Minimal')
.addOption('detailed', 'Detailed')
.setValue(this.plugin.settings.markdownPlugin.template)
.onChange(async (value) => {
this.plugin.settings.markdownPlugin.template = value;
await this.plugin.saveSettings();
})
);
}
}
displayKanbanPluginSettings(containerEl: HTMLElement): void {
containerEl.createEl('h3', { text: 'Kanban Board Plugin' });
new Setting(containerEl)
.setName('Enable Kanban Plugin')
.addToggle((toggle) =>
toggle
.setValue(this.plugin.settings.kanbanPlugin.enabled)
.onChange(async (value) => {
this.plugin.settings.kanbanPlugin.enabled = value;
await this.plugin.saveSettings();
})
);
if (this.plugin.settings.kanbanPlugin.enabled) {
new Setting(containerEl)
.setName('Board Template')
.setDesc('Default board template')
.addDropdown((dropdown) =>
dropdown
.addOption('default', 'Default')
.addOption('agile', 'Agile')
.addOption('gtd', 'Getting Things Done')
.setValue(this.plugin.settings.kanbanPlugin.boardTemplate)
.onChange(async (value) => {
this.plugin.settings.kanbanPlugin.boardTemplate = value;
await this.plugin.saveSettings();
})
);
new Setting(containerEl)
.setName('Default Layout')
.setDesc('How boards are displayed by default')
.addDropdown((dropdown) =>
dropdown
.addOption('columns', 'Columns')
.addOption('rows', 'Rows')
.addOption('grid', 'Grid')
.setValue(this.plugin.settings.kanbanPlugin.defaultLayout)
.onChange(async (value) => {
this.plugin.settings.kanbanPlugin.defaultLayout = value;
await this.plugin.saveSettings();
})
);
}
}
}
Plugin Examples
Example 1: Folder Organization Plugin
Settings Interface:
export interface FolderOrganizerSettings {
enabled: boolean;
organizationHierarchy: string;
createMissingFolders: boolean;
overwriteExisting: boolean;
excludeFolders: string[];
}
Key UI Elements:
- Toggle to enable/disable
- Text area for hierarchy configuration
- Toggle for auto-create folders
- Toggle for overwrite confirmation
- Multi-select or list for excluded folders
- Action button to organize files
Example 2: Markdown Auto-Create Plugin
Settings Interface:
export interface MarkdownAutoCreateSettings {
enabled: boolean;
defaultPath: string;
template: 'default' | 'minimal' | 'detailed';
autoOpenNewFile: boolean;
defaultFrontmatter: string;
hotkey: string;
}
Key UI Elements:
- Toggle to enable/disable
- Text input for default path
- Dropdown for template selection
- Toggle for auto-open
- Text area for frontmatter template
- Display current hotkey (or hotkey picker if available)
Example 3: Kanban Board Plugin
Settings Interface:
export interface KanbanBoardSettings {
enabled: boolean;
boardTemplate: 'default' | 'agile' | 'gtd';
defaultLayout: 'columns' | 'rows' | 'grid';
cardWidth: number;
showDates: boolean;
showTags: boolean;
defaultColumns: string[];
darkMode: boolean;
}
Key UI Elements:
- Toggle to enable/disable
- Dropdown for board templates
- Dropdown for default layout
- Slider for card width
- Toggles for feature flags
- Text area for default columns (comma-separated)
- Color picker for theme customization
Best Practices
- Always save settings - Call
await this.plugin.saveSettings()after any change - Use descriptive names - Users should understand what each setting does
- Group related settings - Use section headers (h3) to organize
- Provide defaults - Always have sensible defaults in
DEFAULT_SETTINGS - Validate input - Check user input before saving (trim, validate paths, etc.)
- Use conditionals wisely - Show/hide advanced options based on basic settings
- Provide feedback - Use
new Notice()to confirm actions - Redraw on enable/disable - Call
this.display()to update UI when features toggle - Document complex settings - Use
.setDesc()liberally - Test multi-step workflows - Ensure settings changes work with all plugins enabled
Testing Your Settings UI
// In your test file
const plugin = new MyPlugin(app, manifest);
plugin.loadSettings();
plugin.registerSettingTab(new UnifiedSettingTab(app, plugin));
// Settings should load without errors
// Check that all toggles/inputs work
// Verify settings persist after reload
// Test conditional displays
This guide provides a foundation for building clean, organized, and user-friendly plugin settings. Adapt these patterns to your specific needs!