Skip to main content
Commands and settings are essential features for making your plugin accessible and configurable. This guide covers how to register commands and create settings interfaces.

Plugin Commands

Commands allow users to trigger plugin functionality from the command palette or via hotkeys.

Basic Command

Register a simple command:
import { Plugin } from 'obsidian';

export default class ExamplePlugin extends Plugin {
  async onload() {
    this.addCommand({
      id: 'example-command',
      name: 'Example command',
      callback: () => {
        console.log('Command executed!');
      }
    });
  }
}
The command ID is automatically prefixed with your plugin ID. Users see the name in the command palette.

Command with Icon

Add an icon to commands that appear in the toolbar:
this.addCommand({
  id: 'open-view',
  name: 'Open custom view',
  icon: 'layout-dashboard',
  callback: () => {
    // Open your view
  }
});

Command Structure

interface Command {
  id: string;              // Unique command ID
  name: string;            // Display name
  icon?: string;           // Optional icon
  mobileOnly?: boolean;    // Only on mobile
  repeatable?: boolean;    // Repeat when holding hotkey
  callback?: () => any;    // Simple callback
  checkCallback?: (checking: boolean) => boolean | void;
  editorCallback?: (editor: Editor, view: MarkdownView) => any;
  editorCheckCallback?: (checking: boolean, editor: Editor, view: MarkdownView) => boolean | void;
  hotkeys?: Hotkey[];      // Default hotkeys
}

Check Callbacks

Use check callbacks to conditionally enable commands:
this.addCommand({
  id: 'format-selection',
  name: 'Format selected text',
  checkCallback: (checking: boolean) => {
    const view = this.app.workspace.getActiveViewOfType(MarkdownView);
    
    // Check if command should be available
    if (view && view.editor.somethingSelected()) {
      if (!checking) {
        // Only execute when not checking
        const selection = view.editor.getSelection();
        view.editor.replaceSelection(selection.toUpperCase());
      }
      return true; // Command is available
    }
    
    return false; // Command is hidden
  }
});
When checking is true, the command palette is just checking if the command should be shown. Don’t perform the action during checking.

Editor Commands

Commands that work with the active editor:

Editor Callback

this.addCommand({
  id: 'insert-date',
  name: 'Insert current date',
  editorCallback: (editor: Editor, view: MarkdownView) => {
    const date = new Date().toISOString().split('T')[0];
    editor.replaceSelection(date);
  }
});

Editor Check Callback

this.addCommand({
  id: 'toggle-bold',
  name: 'Toggle bold formatting',
  editorCheckCallback: (checking: boolean, editor: Editor, view: MarkdownView) => {
    const selection = editor.getSelection();
    
    if (selection) {
      if (!checking) {
        if (selection.startsWith('**') && selection.endsWith('**')) {
          // Remove bold
          editor.replaceSelection(selection.slice(2, -2));
        } else {
          // Add bold
          editor.replaceSelection(`**${selection}**`);
        }
      }
      return true;
    }
    
    return false;
  }
});

Default Hotkeys

Avoid setting default hotkeys when possible to prevent conflicts with user-configured hotkeys.
If you must set a default hotkey:
this.addCommand({
  id: 'quick-action',
  name: 'Quick action',
  hotkeys: [
    {
      modifiers: ['Mod', 'Shift'],
      key: 'q'
    }
  ],
  callback: () => {
    // Action
  }
});
Modifier keys:
  • 'Mod' - Cmd on macOS, Ctrl elsewhere
  • 'Ctrl' - Ctrl on all platforms
  • 'Meta' - Cmd on macOS, Win key elsewhere
  • 'Shift' - Shift key
  • 'Alt' - Alt/Option key

Repeatable Commands

Allow commands to repeat when holding the hotkey:
this.addCommand({
  id: 'increase-heading',
  name: 'Increase heading level',
  repeatable: true,
  editorCallback: (editor) => {
    const cursor = editor.getCursor();
    const line = editor.getLine(cursor.line);
    if (line.startsWith('#')) {
      editor.setLine(cursor.line, '#' + line);
    }
  }
});

Remove Commands

Dynamically remove commands (rarely needed):
// Remove a command
this.removeCommand('plugin-id:command-id');

Plugin Settings

Create a settings tab to let users configure your plugin.

Define Settings Interface

interface ExampleSettings {
  apiKey: string;
  enableFeature: boolean;
  refreshInterval: number;
  theme: 'light' | 'dark' | 'auto';
}

const DEFAULT_SETTINGS: ExampleSettings = {
  apiKey: '',
  enableFeature: true,
  refreshInterval: 60,
  theme: 'auto'
};

Create Settings Tab

import { App, PluginSettingTab, Setting } from 'obsidian';
import ExamplePlugin from './main';

export class ExampleSettingTab extends PluginSettingTab {
  plugin: ExamplePlugin;

  constructor(app: App, plugin: ExamplePlugin) {
    super(app, plugin);
    this.plugin = plugin;
  }

  display(): void {
    const { containerEl } = this;
    containerEl.empty();

    containerEl.createEl('h2', { text: 'Example Plugin Settings' });

    // Text input
    new Setting(containerEl)
      .setName('API Key')
      .setDesc('Enter your API key for external service')
      .addText(text => text
        .setPlaceholder('Enter your key')
        .setValue(this.plugin.settings.apiKey)
        .onChange(async (value) => {
          this.plugin.settings.apiKey = value;
          await this.plugin.saveSettings();
        }));

    // Toggle
    new Setting(containerEl)
      .setName('Enable feature')
      .setDesc('Turn on the special feature')
      .addToggle(toggle => toggle
        .setValue(this.plugin.settings.enableFeature)
        .onChange(async (value) => {
          this.plugin.settings.enableFeature = value;
          await this.plugin.saveSettings();
        }));

    // Slider
    new Setting(containerEl)
      .setName('Refresh interval')
      .setDesc('How often to refresh (in seconds)')
      .addSlider(slider => slider
        .setLimits(10, 300, 10)
        .setValue(this.plugin.settings.refreshInterval)
        .setDynamicTooltip()
        .onChange(async (value) => {
          this.plugin.settings.refreshInterval = value;
          await this.plugin.saveSettings();
        }));

    // Dropdown
    new Setting(containerEl)
      .setName('Theme')
      .setDesc('Choose 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 as any;
          await this.plugin.saveSettings();
        }));
  }
}

Register Settings Tab

export default class ExamplePlugin extends Plugin {
  settings: ExampleSettings;

  async onload() {
    await this.loadSettings();
    
    // Add settings tab
    this.addSettingTab(new ExampleSettingTab(this.app, this));
  }

  async loadSettings() {
    this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
  }

  async saveSettings() {
    await this.saveData(this.settings);
  }
}

Advanced Settings

Section Headings

new Setting(containerEl)
  .setName('Advanced options')
  .setHeading();

new Setting(containerEl)
  .setName('Debug mode')
  .addToggle(...);

Setting with Button

new Setting(containerEl)
  .setName('Clear cache')
  .setDesc('Remove all cached data')
  .addButton(button => button
    .setButtonText('Clear')
    .setWarning()
    .onClick(async () => {
      await this.plugin.clearCache();
      new Notice('Cache cleared!');
    }));

Disabled Settings

const setting = new Setting(containerEl)
  .setName('Pro feature')
  .addToggle(toggle => toggle
    .setValue(false));

if (!this.plugin.isPro) {
  setting.setDisabled(true);
}

Multiple Inputs

new Setting(containerEl)
  .setName('Date range')
  .addText(text => text
    .setPlaceholder('Start date')
    .onChange(async (value) => {
      this.plugin.settings.startDate = value;
      await this.plugin.saveSettings();
    }))
  .addText(text => text
    .setPlaceholder('End date')
    .onChange(async (value) => {
      this.plugin.settings.endDate = value;
      await this.plugin.saveSettings();
    }));

Custom Setting Content

const setting = new Setting(containerEl)
  .setName('Custom control');

setting.controlEl.createDiv({ cls: 'custom-control' }, (el) => {
  // Build custom UI
  const button = el.createEl('button', { text: 'Custom' });
  button.addEventListener('click', () => {
    // Handle click
  });
});

Settings Best Practices

1
Use Descriptive Names
2
Make setting names clear and descriptive:
3
// Good
new Setting(containerEl)
  .setName('Automatically save on window blur')
  .setDesc('Save your work when switching to another application');

// Avoid
new Setting(containerEl)
  .setName('Auto-save');
4
Validate Input
5
Validate user input before saving:
6
new Setting(containerEl)
  .setName('Port number')
  .addText(text => text
    .setValue(String(this.plugin.settings.port))
    .onChange(async (value) => {
      const port = parseInt(value);
      if (port > 0 && port < 65536) {
        this.plugin.settings.port = port;
        await this.plugin.saveSettings();
      } else {
        new Notice('Invalid port number');
      }
    }));
7
Provide Defaults
8
Always provide sensible defaults:
9
const DEFAULT_SETTINGS: ExampleSettings = {
  // All settings have defaults
  apiKey: '',
  enabled: true,
  timeout: 5000
};

async loadSettings() {
  // Merge with defaults
  this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
}
10
React to Setting Changes
11
Update plugin behavior when settings change:
12
async saveSettings() {
  await this.saveData(this.settings);
  
  // Apply settings
  this.updateInterval();
  this.refreshUI();
}

External Settings Changes

Handle when settings are modified externally (e.g., by sync):
export default class ExamplePlugin extends Plugin {
  async onload() {
    await this.loadSettings();
  }

  onExternalSettingsChange() {
    // Reload settings from disk
    this.loadSettings();
  }

  async loadSettings() {
    this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
    // Apply new settings
    this.applySettings();
  }
}

UI Components

Build modals and user interfaces

Editor Integration

Work with the editor API

Build docs developers (and LLMs) love