--- name: obsidian description: Comprehensive guidelines for Obsidian.md plugin development including all 36 ESLint rules from eslint-plugin-obsidianmd v0.2.8, TypeScript best practices, memory management, API usage (requestUrl vs fetch), UI/UX standards, locale file sentence-case enforcement, popout window compatibility, and submission requirements. Use when working with Obsidian plugins, main.ts files, manifest.json, Plugin class, MarkdownView, TFile, vault operations, or any Obsidian API development. license: MIT metadata: version: 1.6.0 --- # Obsidian Plugin Development Guidelines Follow these comprehensive guidelines derived from the official Obsidian ESLint plugin rules, submission requirements, and best practices. ## Getting Started ### Quick Start Tool For new plugin projects, an interactive boilerplate generator is available: - **Script**: `tools/create-plugin.js` in the skill repository - **Command**: Invoke `create-plugin` using your agent's method (`/create-plugin`, `$create-plugin`, or `@create-plugin`) - Generates minimal, best-practice boilerplate with no sample code - Detects existing projects and only adds missing files Recommend the boilerplate generator when users ask how to create a new plugin, want to start a new project, or need help setting up the basic structure. --- ## Rules Reference (eslint-plugin-obsidianmd v0.2.8) ### Submission & Naming | # | Rule | ✅ Do | ❌ Don't | |---|------|--------|----------| | 1 | Plugin ID | Omit "obsidian"; don't end with "plugin" | Include "obsidian" or end with "plugin" | | 2 | Plugin name | Omit "Obsidian"; don't end with "Plugin" | Include "Obsidian" or end with "Plugin" | | 3 | Plugin name | Don't start with "Obsi" or end with "dian" | Start with "Obsi" or end with "dian" | | 4 | Description | Omit "Obsidian", "This plugin", etc. | Use "Obsidian" or "This plugin" | | 5 | Description | End with `.?!)` punctuation | Leave description without terminal punctuation | ### Memory & Lifecycle | # | Rule | ✅ Do | ❌ Don't | |---|------|--------|----------| | 6 | Event cleanup | Use `registerEvent()` for automatic cleanup | Register events without cleanup | | 7 | View references | Return views/components directly | Store view references in plugin properties or pass plugin as component to `MarkdownRenderer` | | 8 | Leaf detachment | Let Obsidian handle leaf cleanup | Call `detachLeavesOfType()` in `onunload` | ### Type Safety | # | Rule | ✅ Do | ❌ Don't | |---|------|--------|----------| | 9 | TFile/TFolder | Use `instanceof` for type checking | Cast to TFile/TFolder; use `any`; use `var` | | 10 | DOM instanceof | Use `.instanceOf(T)` for DOM Nodes/UIEvents | Use `instanceof` for cross-window DOM checks | ### UI/UX | # | Rule | ✅ Do | ❌ Don't | |---|------|--------|----------| | 11 | UI text | Sentence case — "Advanced settings" | Title Case — "Advanced Settings" | | 12 | JSON locale | Sentence case in JSON locale files (`recommendedWithLocalesEn`) | Title case in locale JSON | | 13 | TS/JS locale | Sentence case in TS/JS locale modules | Title case in locale modules | | 14 | Command names | Omit "command" in command names/IDs | Include "command" in names/IDs | | 15 | Command IDs | Omit plugin ID/name from command IDs/names | Duplicate plugin ID in command IDs | | 16 | Hotkeys | No default hotkeys | Set default hotkeys | | 17 | Settings headings | Use `.setHeading()` | Create manual HTML headings; use "General", "settings", or plugin name in headings | ### API Best Practices | # | Rule | ✅ Do | ❌ Don't | |---|------|--------|----------| | 18 | Active file edits | Use Editor API | Use `Vault.modify()` for active file edits | | 19 | Background file mods | Use `Vault.process()` | Use `Vault.modify()` for background modifications | | 20 | File deletion | Use `FileManager.trashFile()` | Use `Vault.trash()` or `Vault.delete()` directly | | 21 | File lookup | Use `Vault.getAbstractFileByPath()` | Iterate all files with `Vault.getFiles().find()` | | 22 | User paths | Use `normalizePath()` | Hardcode `.obsidian` path; use raw user paths | | 23 | OS detection | Use `Platform` API | Use `navigator.platform`/`userAgent` | | 24 | Network requests | Use `requestUrl()` | Use `fetch()` | | 25 | Logging | Minimize console logging; none in `onload`/`onunload` in production | Use `console.log` in `onload`/`onunload` | | 26 | Input suggest | Use built-in `AbstractInputSuggest` | Copy Liam's `TextInputSuggest` implementation | | 27 | API compatibility | Check `minAppVersion` for API availability | Use APIs not available in declared minAppVersion | | 28 | Language detection | Use Obsidian's `getLanguage()` | Use `localStorage.getItem('language')` or `i18next-browser-languagedetector` | ### Popout Window Compatibility | # | Rule | ✅ Do | ❌ Don't | |---|------|--------|----------| | 29 | Document/Window | Use `activeDocument` and `activeWindow` | Use global `document` and `window` | | 30 | Timers | Use `activeWindow.setTimeout()`, `setInterval()`, etc. | Use bare `setTimeout()`, `setInterval()` | ### Event Handling | # | Rule | ✅ Do | ❌ Don't | |---|------|--------|----------| | 31 | Editor drop/paste | Check `evt.defaultPrevented` and call `evt.preventDefault()` | Handle editor-drop/paste without checking defaultPrevented | ### Styling | # | Rule | ✅ Do | ❌ Don't | |---|------|--------|----------| | 32 | CSS variables | Use Obsidian CSS variables for all styling | Hardcode colors, sizes, or spacing | | 33 | CSS scope | Scope CSS to plugin containers | Use broad CSS selectors | | 34 | Style elements | Use `styles.css` file (`no-forbidden-elements`) | Create `` or `