Files
Obsidian-Plugins/op.docs/PLUGIN_SETTINGS_UI_GUIDE.md
2026-06-19 16:52:38 -04:00

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

  1. Core Concepts
  2. Setting Types
  3. UI Organization Patterns
  4. Multi-Plugin Architecture
  5. 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
// 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

  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

// 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!