Obsidian Plugin Development
Comprehensive guidance for building Obsidian community plugins using TypeScript.
Plugin Architecture
Obsidian plugins are TypeScript bundles that extend the app's functionality. Every plugin:
- Extends the
Plugin class from obsidian
- Implements
onload() for initialization
- Implements
onunload() for cleanup
- Bundles to a single
main.js file via esbuild
Plugin Lifecycle
import { Plugin } from 'obsidian';
export default class MyPlugin extends Plugin {
async onload() {
// Configure resources, register commands, views, etc.
}
async onunload() {
// Release resources (automatic for register* methods)
}
}
Project Structure
Organize code across multiple files:
src/
main.ts # Plugin entry point, lifecycle only
settings.ts # Settings interface and tab
commands/ # Command implementations
ui/ # Modals, views, components
utils/ # Helpers and constants
types.ts # TypeScript interfaces
Keep main.ts minimal - delegate logic to modules.
Core Capabilities
Commands
Register user-invocable actions:
this.addCommand({
id: 'my-command', // Stable ID, never change after release
name: 'My Command', // User-visible name
callback: () => { ... }, // Or editorCallback, checkCallback
});
Use editorCallback for editor access, checkCallback for conditional availability.
Settings
Persist user configuration:
interface MySettings { enabled: boolean; }
const DEFAULT_SETTINGS: Partial<MySettings> = { enabled: true };
// In onload:
this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
this.addSettingTab(new MySettingTab(this.app, this));
// Save changes:
await this.saveData(this.settings);
UI Components
- Ribbon icons:
this.addRibbonIcon('icon', 'tooltip', callback)
- Status bar:
this.addStatusBarItem().setText('text') (desktop only)
- Modals: Extend
Modal or SuggestModal/FuzzySuggestModal
- Views: Extend
ItemView, register with registerView()
- Notices:
new Notice('message')
Events and Cleanup
Always use register* methods for automatic cleanup:
this.registerEvent(this.app.vault.on('create', callback));
this.registerDomEvent(document, 'click', callback);
this.registerInterval(window.setInterval(callback, 1000));
Development Workflow
Setup
# Clone sample plugin or create from template
npm install
npm run dev # Watch mode
npm run build # Production build
Testing
Copy build artifacts to vault:
<Vault>/.obsidian/plugins/<plugin-id>/
main.js
manifest.json
styles.css (optional)
Reload Obsidian and enable in Settings → Community plugins.
Debugging
- Open DevTools: Ctrl+Shift+I (Windows/Linux) or Cmd+Option+I (macOS)
- Use
console.log() for debugging
- Check Console tab for errors
Manifest (manifest.json)
Required fields:
{
"id": "plugin-id",
"name": "Plugin Name",
"version": "1.0.0",
"minAppVersion": "1.0.0",
"description": "What it does",
"author": "Your Name",
"isDesktopOnly": false
}
Rules:
- Never change
id after release
- Use semantic versioning (x.y.z)
- Keep
minAppVersion accurate
- Set
isDesktopOnly: true only if using desktop-only APIs
Best Practices
Performance
- Keep startup light - defer heavy work
- Lazy-initialize expensive resources
- Batch disk access, debounce file events
Security & Privacy
- Default to local/offline operation
- No hidden telemetry - require explicit opt-in
- Never execute remote code
- Disclose external services used
- Minimize vault access scope
Code Quality
- TypeScript with
"strict": true
- Bundle everything into main.js
- Use async/await, handle errors gracefully
- Keep main.ts minimal, split into modules
Mobile Compatibility
- Test on iOS and Android if
isDesktopOnly: false
- Avoid desktop-only APIs (Node, Electron)
- Mind memory constraints
Common Patterns
File Operations
// Read file
const content = await this.app.vault.read(file);
// Write file
await this.app.vault.modify(file, newContent);
// Create file
await this.app.vault.create(path, content);
// Get active file
const file = this.app.workspace.getActiveFile();
Editor Operations
// In editorCallback:
const selection = editor.getSelection();
editor.replaceSelection(newText);
editor.getCursor(); // { line, ch }
editor.setLine(lineNum, text);
Workspace
// Get active view
const view = this.app.workspace.getActiveViewOfType(MarkdownView);
// Open file
await this.app.workspace.openLinkText(path, '');
// Get leaves of type
const leaves = this.app.workspace.getLeavesOfType(VIEW_TYPE);
Additional Resources
Reference Files
For detailed API patterns and code examples, consult:
Core Development:
references/agents-guide.md - Comprehensive AI-oriented development guide with coding conventions, file structure, common tasks, and do/don't guidelines
references/ui-components.md - Detailed UI component examples (commands, views, modals, settings)
references/publishing.md - Publishing workflow and community submission process
Vault & Files:
references/vault-api.md - File operations, TFile/TFolder, read/cachedRead, process(), vault events
Editor & Markdown:
references/editor-extensions.md - CodeMirror 6 state fields, view plugins, decorations, widgets
references/markdown-processing.md - Post processors, code block processors for rendering content
UI & Styling:
references/views.md - Complete views guide: ItemView, workspace, leaves, deferred views, state serialization
references/icons.md - Lucide icons, setIcon(), addIcon() for custom SVGs, sizing
references/status-bar.md - Status bar items, icons, dynamic updates (desktop only)
references/context-menus.md - Menu class, file-menu/editor-menu events, submenus
references/html-styling.md - createEl() helpers, CSS variables, dynamic styling
references/rtl-support.md - Right-to-left language support, logical CSS properties, internationalization
Frameworks:
references/frameworks.md - React integration overview, mounting in views
references/svelte.md - Comprehensive Svelte 5 guide with runes, components, and patterns
Advanced:
references/performance.md - onLayoutReady, startup optimization, deferred views, pitfalls
references/multi-window.md - Pop-out window support, element.doc/win, instanceOf()
references/secrets.md - SecretStorage API for secure API key/token storage
Testing & Development:
references/testing-guide.md - Testing workflow, debugging, mobile testing, pre-release checklist
Example Files
Working examples in examples/:
examples/sample-main.ts - Complete main.ts with all common patterns
examples/sample-settings.ts - Settings interface and tab implementation
examples/sample-view.ts - Custom ItemView with state persistence, actions, and filtering
examples/sample-editor-extension.ts - CodeMirror 6 state fields, view plugins, decorations
examples/sample-markdown-processor.ts - Post processors, code block processors, widgets
External Documentation
Converted and distributed by TomeVault — claim your Tome and manage your conversions.
1---2name: obsidian-plugin-development3description: This skill should be used when the user asks to "create an obsidian plugin", "build an obsidian plugin", "add a command to obsidian", "create a modal", "add a settings tab", "create a custom view", "add a sidebar view", "add a status bar item", "add an icon", "use setIcon", "support RTL languages", "test my plugin", "debug my plugin", "publish to obsidian community plugins", "create an editor extension", "add CodeMirror decorations", "add a context menu", "use the vault API", "optimize plugin performance", "use React in obsidian", "use Svelte in obsidian", "store API keys securely", "handle pop-out windows", "create a markdown processor", "manage workspace leaves", or mentions Obsidian plugin development, the Obsidian API, ItemView, WorkspaceLeaf, Lucide icons, right-to-left, CodeMirror 6, SecretStorage, or TypeScript plugins for Obsidian. Use when this capability is needed.4---56# Obsidian Plugin Development78Comprehensive guidance for building Obsidian community plugins using TypeScript.910## Plugin Architecture1112Obsidian plugins are TypeScript bundles that extend the app's functionality. Every plugin:1314- Extends the `Plugin` class from `obsidian`15- Implements `onload()` for initialization16- Implements `onunload()` for cleanup17- Bundles to a single `main.js` file via esbuild1819### Plugin Lifecycle2021```ts22import { Plugin } from 'obsidian';2324export default class MyPlugin extends Plugin {25 async onload() {26 // Configure resources, register commands, views, etc.27 }2829 async onunload() {30 // Release resources (automatic for register* methods)31 }32}33```3435### Project Structure3637Organize code across multiple files:3839```40src/41 main.ts # Plugin entry point, lifecycle only42 settings.ts # Settings interface and tab43 commands/ # Command implementations44 ui/ # Modals, views, components45 utils/ # Helpers and constants46 types.ts # TypeScript interfaces47```4849Keep `main.ts` minimal - delegate logic to modules.5051## Core Capabilities5253### Commands5455Register user-invocable actions:5657```ts58this.addCommand({59 id: 'my-command', // Stable ID, never change after release60 name: 'My Command', // User-visible name61 callback: () => { ... }, // Or editorCallback, checkCallback62});63```6465Use `editorCallback` for editor access, `checkCallback` for conditional availability.6667### Settings6869Persist user configuration:7071```ts72interface MySettings { enabled: boolean; }73const DEFAULT_SETTINGS: Partial<MySettings> = { enabled: true };7475// In onload:76this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());77this.addSettingTab(new MySettingTab(this.app, this));7879// Save changes:80await this.saveData(this.settings);81```8283### UI Components8485- **Ribbon icons**: `this.addRibbonIcon('icon', 'tooltip', callback)`86- **Status bar**: `this.addStatusBarItem().setText('text')` (desktop only)87- **Modals**: Extend `Modal` or `SuggestModal`/`FuzzySuggestModal`88- **Views**: Extend `ItemView`, register with `registerView()`89- **Notices**: `new Notice('message')`9091### Events and Cleanup9293Always use `register*` methods for automatic cleanup:9495```ts96this.registerEvent(this.app.vault.on('create', callback));97this.registerDomEvent(document, 'click', callback);98this.registerInterval(window.setInterval(callback, 1000));99```100101## Development Workflow102103### Setup104105```bash106# Clone sample plugin or create from template107npm install108npm run dev # Watch mode109npm run build # Production build110```111112### Testing113114Copy build artifacts to vault:115```116<Vault>/.obsidian/plugins/<plugin-id>/117 main.js118 manifest.json119 styles.css (optional)120```121122Reload Obsidian and enable in Settings → Community plugins.123124### Debugging125126- Open DevTools: Ctrl+Shift+I (Windows/Linux) or Cmd+Option+I (macOS)127- Use `console.log()` for debugging128- Check Console tab for errors129130## Manifest (manifest.json)131132Required fields:133134```json135{136 "id": "plugin-id",137 "name": "Plugin Name",138 "version": "1.0.0",139 "minAppVersion": "1.0.0",140 "description": "What it does",141 "author": "Your Name",142 "isDesktopOnly": false143}144```145146**Rules:**147- Never change `id` after release148- Use semantic versioning (x.y.z)149- Keep `minAppVersion` accurate150- Set `isDesktopOnly: true` only if using desktop-only APIs151152## Best Practices153154### Performance155156- Keep startup light - defer heavy work157- Lazy-initialize expensive resources158- Batch disk access, debounce file events159160### Security & Privacy161162- Default to local/offline operation163- No hidden telemetry - require explicit opt-in164- Never execute remote code165- Disclose external services used166- Minimize vault access scope167168### Code Quality169170- TypeScript with `"strict": true`171- Bundle everything into main.js172- Use async/await, handle errors gracefully173- Keep main.ts minimal, split into modules174175### Mobile Compatibility176177- Test on iOS and Android if `isDesktopOnly: false`178- Avoid desktop-only APIs (Node, Electron)179- Mind memory constraints180181## Common Patterns182183### File Operations184185```ts186// Read file187const content = await this.app.vault.read(file);188189// Write file190await this.app.vault.modify(file, newContent);191192// Create file193await this.app.vault.create(path, content);194195// Get active file196const file = this.app.workspace.getActiveFile();197```198199### Editor Operations200201```ts202// In editorCallback:203const selection = editor.getSelection();204editor.replaceSelection(newText);205editor.getCursor(); // { line, ch }206editor.setLine(lineNum, text);207```208209### Workspace210211```ts212// Get active view213const view = this.app.workspace.getActiveViewOfType(MarkdownView);214215// Open file216await this.app.workspace.openLinkText(path, '');217218// Get leaves of type219const leaves = this.app.workspace.getLeavesOfType(VIEW_TYPE);220```221222## Additional Resources223224### Reference Files225226For detailed API patterns and code examples, consult:227228**Core Development:**229- **`references/agents-guide.md`** - Comprehensive AI-oriented development guide with coding conventions, file structure, common tasks, and do/don't guidelines230- **`references/ui-components.md`** - Detailed UI component examples (commands, views, modals, settings)231- **`references/publishing.md`** - Publishing workflow and community submission process232233**Vault & Files:**234- **`references/vault-api.md`** - File operations, TFile/TFolder, read/cachedRead, process(), vault events235236**Editor & Markdown:**237- **`references/editor-extensions.md`** - CodeMirror 6 state fields, view plugins, decorations, widgets238- **`references/markdown-processing.md`** - Post processors, code block processors for rendering content239240**UI & Styling:**241- **`references/views.md`** - Complete views guide: ItemView, workspace, leaves, deferred views, state serialization242- **`references/icons.md`** - Lucide icons, setIcon(), addIcon() for custom SVGs, sizing243- **`references/status-bar.md`** - Status bar items, icons, dynamic updates (desktop only)244- **`references/context-menus.md`** - Menu class, file-menu/editor-menu events, submenus245- **`references/html-styling.md`** - createEl() helpers, CSS variables, dynamic styling246- **`references/rtl-support.md`** - Right-to-left language support, logical CSS properties, internationalization247248**Frameworks:**249- **`references/frameworks.md`** - React integration overview, mounting in views250- **`references/svelte.md`** - Comprehensive Svelte 5 guide with runes, components, and patterns251252**Advanced:**253- **`references/performance.md`** - onLayoutReady, startup optimization, deferred views, pitfalls254- **`references/multi-window.md`** - Pop-out window support, element.doc/win, instanceOf()255- **`references/secrets.md`** - SecretStorage API for secure API key/token storage256257**Testing & Development:**258- **`references/testing-guide.md`** - Testing workflow, debugging, mobile testing, pre-release checklist259260### Example Files261262Working examples in `examples/`:263264- **`examples/sample-main.ts`** - Complete main.ts with all common patterns265- **`examples/sample-settings.ts`** - Settings interface and tab implementation266- **`examples/sample-view.ts`** - Custom ItemView with state persistence, actions, and filtering267- **`examples/sample-editor-extension.ts`** - CodeMirror 6 state fields, view plugins, decorations268- **`examples/sample-markdown-processor.ts`** - Post processors, code block processors, widgets269270### External Documentation271272- [Obsidian API Docs](https://docs.obsidian.md)273- [Developer Policies](https://docs.obsidian.md/Developer+policies)274- [Plugin Guidelines](https://docs.obsidian.md/Plugins/Releasing/Plugin+guidelines)275- [Sample Plugin](https://github.com/obsidianmd/obsidian-sample-plugin)276277---278> Converted and distributed by [TomeVault](https://tomevault.io/claim/ryanhudson) — claim your Tome and manage your conversions.279<!-- tomevault:4.0:skill_md:2026-04-16 -->