# 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 1. [Core Concepts](#core-concepts) 2. [Setting Types](#setting-types) 3. [UI Organization Patterns](#ui-organization-patterns) 4. [Multi-Plugin Architecture](#multi-plugin-architecture) 5. [Plugin Examples](#plugin-examples) ## Core Concepts ### Settings Interface & Settings Tab Every plugin has: 1. **Settings Interface** - TypeScript interface defining your data structure 2. **Settings Tab Class** - Extends `PluginSettingTab` and defines the UI ```typescript // 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. ```typescript 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: ```typescript 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: ```typescript 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: ```typescript 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: ```typescript 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: ```typescript 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: ```typescript 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: ```typescript 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: ```typescript 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: ```typescript 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: ```typescript 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 ```typescript // 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 ```typescript // 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:** ```typescript 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:** ```typescript 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:** ```typescript 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 1. **Always save settings** - Call `await this.plugin.saveSettings()` after any change 2. **Use descriptive names** - Users should understand what each setting does 3. **Group related settings** - Use section headers (h3) to organize 4. **Provide defaults** - Always have sensible defaults in `DEFAULT_SETTINGS` 5. **Validate input** - Check user input before saving (trim, validate paths, etc.) 6. **Use conditionals wisely** - Show/hide advanced options based on basic settings 7. **Provide feedback** - Use `new Notice()` to confirm actions 8. **Redraw on enable/disable** - Call `this.display()` to update UI when features toggle 9. **Document complex settings** - Use `.setDesc()` liberally 10. **Test multi-step workflows** - Ensure settings changes work with all plugins enabled ## Testing Your Settings UI ```typescript // 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!