| name | obsidian-plugin-dev |
| description | Obsidian plugin development guide — architecture, API patterns, and conventions. TRIGGER when: writing or editing Obsidian plugin source code (TypeScript files importing from "obsidian"), creating plugin components (settings tabs, modals, views, commands), working with the Vault API, or scaffolding new plugin features. DO NOT TRIGGER when: general TypeScript work unrelated to Obsidian, or when only debugging/testing/releasing (use sibling skills instead).
|
| user-invocable | false |
| allowed-tools | ["Read","Edit","Write","Glob","Grep","Bash","Agent"] |
Obsidian Plugin Development
Plugin Anatomy
plugin-root/
├── src/
│ └── main.ts # Entry point — extends Plugin
├── manifest.json # Plugin metadata (id, name, version, minAppVersion)
├── package.json # Node dependencies
├── tsconfig.json # TypeScript config (target ES2018, module ESNext)
├── esbuild.config.mjs # Build script
├── styles.css # Optional plugin styles
└── versions.json # Maps plugin version → minimum Obsidian version
Entry Point Pattern
import { Plugin, PluginSettingTab, Setting, Notice } from "obsidian";
interface MyPluginSettings {
}
const DEFAULT_SETTINGS: MyPluginSettings = {
};
export default class MyPlugin extends Plugin {
settings: MyPluginSettings;
async onload() {
await this.loadSettings();
}
onunload() {
}
async loadSettings() {
this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
}
async saveSettings() {
await this.saveData(this.settings);
}
}
Key API Classes
Registration Pattern
Always use this.register*() or this.addChild() — Obsidian auto-cleans on unload:
this.addCommand({
id: "my-command",
name: "Do something",
callback: () => { },
});
this.registerEvent(
this.app.vault.on("modify", (file) => { })
);
this.registerInterval(
window.setInterval(() => { }, 30000)
);
this.registerDomEvent(document, "click", (evt) => { });
this.addRibbonIcon("sync", "Sync", () => { });
const statusBar = this.addStatusBarItem();
statusBar.setText("Synced");
File Operations
See vault-api.md for the complete Vault API reference including:
- Reading/writing text and binary files
- Creating and deleting files
- File metadata and stat access
- The
adapter API for arbitrary path access
plugin.loadData() / plugin.saveData() for plugin state
Platform Considerations
See platform.md for:
- Mobile compatibility (
Platform.isMobile, Platform.isDesktop)
requestUrl for HTTP (no fetch — CORS issues on desktop)
- Web Crypto API instead of Node
crypto
visibilitychange for mobile lifecycle
- Performance constraints on mobile
Build System
Standard esbuild config (see build-system.md):
- Entry:
src/main.ts → main.js
- Format: CommonJS (Obsidian requires it)
- External:
["obsidian", "electron", "@codemirror/*", "@lezer/*"]
- Target:
es2018
- Bundle everything except externals into single
main.js
Conventions
- One plugin class —
export default class extending Plugin
- Settings always typed — interface + defaults +
Object.assign merge
- Register everything — never manage cleanup manually
requestUrl over fetch — works cross-platform, bypasses CORS
crypto.subtle over Node crypto — works in Obsidian's runtime
- No Node.js builtins — no
fs, path, crypto, Buffer (use ArrayBuffer)
- Vault API over adapter — prefer
vault.read() over vault.adapter.read() for tracked files
- Minimum version — set
minAppVersion in manifest.json to the oldest supported version