obsidian
Comprehensive guidelines for Obsidian.md plugin development including ESLint rules from eslint-plugin-obsidianmd v0.4.2, TypeScript best practices, memory…
Skill by gapmiss
Install
Works with Claude Code, Claude apps, Codex, Cursor, Gemini CLI, GitHub Copilot, VS Code, OpenCode, Goose, Windsurf. Read the repository before you give any tool access to your data.
npx agentmarket install skill/gapmiss-obsidian
Asks which of your tools to add it to (Claude Code, Codex, Cursor, VS Code, Windsurf, Gemini CLI, Claude Desktop) and shows the change first. See https://agentmart.dev/cli.
Get the weekly diff
The weekly diff: one email a week with new agent tools, notable updates and grade changes, plus security alerts for tools you follow. Double opt-in, one-click unsubscribe.
About
Use cases: design, backend
Reviews
Write a reviewNo reviews yet. Installed it? Reviews come from verified installers only. How reviews work
Permissions
What this skill can do and what it asks for, from its SKILL.md. Checked Oct 25, 2026.
- Tools
- No tools found
- Secrets
- None asked for
- Install scripts
- None found
Network hosts it talks to 1
codeload.github.com· found in install command
Files it touches
~/.claude/skills/obsidian
What's inside
- Skill name
obsidian- Description
- Comprehensive guidelines for Obsidian.md plugin development including ESLint rules from eslint-plugin-obsidianmd v0.4.2, TypeScript best practices, memory management, API usage (requestUrl vs fetch), UI/UX standards, popout window compatibility, community.obsidian.md submission process, and Scorecard optimization. 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.11.1
- Instructions
- 358 lines in SKILL.md · view on GitHub
Before installing
- Instructions call the network
Files (11)
- SKILL.md
- accessibility.md · reference/
- code-quality.md · reference/
- community-scanner.md · reference/
- css-styling.md · reference/
- eslint-setup.md · reference/
- file-operations.md · reference/
- memory-management.md · reference/
- submission.md · reference/
- type-safety.md · reference/
- ui-ux.md · reference/
SKILL.md (first lines)
# 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.4.2)
### 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 |
| 6a | DOM events | Use `registerDomEvent()` on the plugin or owning component | Pair `addEventListener` with manual `removeEventListener` 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"; use `acronyms`/`brands` options for proper names | 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 |
> **Note (v0.4.0):** `ui/sentence-case` is now enabled (`warn`) and enforced on inline UI strings — it was disabled in v0.3.0. Use the `recommendedWithLocalesEn` config to also check English locale files (rules 12–13).
| 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 |
### Declarative Settings (1.13.0+)
All four `settings-tab` rules ship as `warn` in `recommended`. Rules 17a/17c/17d read `minAppVersion` from `manifest.json`; 17b is **not** version-gated.
| # | Rule | ✅ Do | ❌ Don't |
|---|------|--------|----------|
| 17a | `settings-tab/require-display` | Keep `display()` when `minAppVersion < 1.13.0` | Ship declarative-only settings that render nothing on older Obsidian |
| 17b | `settings-tab/prefer-setting-definitions` | Implement `getSettingDefinitions()` on every `PluginSettingTab` | Rely on `display()` alone — settings won't appear in 1.13+ global search |
| 17c | `settings-tab/prefer-update-over-display` | Call `this.update()` to re-render declarative settings | Call `this.display()` — it's bypassed when definitions are non-empty |
| 17d | `settings-tab/no-deprecated-display` | Delete `display()` once `minAppVersion >= 1.13.0` and definitions exist | Leave a dead `display()` behind (auto-fixable) |
| — | Settings data | Keep all persisted data inside `plugin.settings` | Store sibling keys via `saveData()` — auto-persist clobbers them |
> **Detection caveat:** these rules match a bare `extends PluginSettingTab` only. `extends obsidian.PluginSettingTab` is out of scope and won't be flagged — but the underlying guidance still applies.
### 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()` |
| 24a | Response typing | Parse `response.text` and validate/cast once at the API boundary | Pass `response.json` (typed `any`) into plugin code |
| 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 |
| 26a | Confirm dialogs | Use built-in `ConfirmationModal` (1.13.0+); use `FileManager.promptForDeletion()` to confirm and delete files | Hand-build a `Modal` subclass with its own confirm/cancel button row |
| 27 | API compatibility | Check `minAppVersion` for API availability (e.g., `getSettingDefinitions()` requires 1.13.0) | 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` |
| 29a | Getter capture | Capture `activeDocument` in a variable when the same document is needed later | Call `activeDocument` at setup and again at cleanup — it follows focus and may return different documents |
| 30 | Timers | Use `window.setTimeout()`, `window.setInterval()`, `window.requestAnimationFrame()`, etc. (exception to rule 29) | Use bare `setTimeout()`/`setInterval()`, or `activeWindow.setTimeout()` — `prefer-window-timers` flags both |
| 31 | Main workspace UI | Use `this.app.workspace.containerEl.ownerDocument` from settings | Use `activeDocument` to update main workspace from settings window |
> **Note (v0.4.0):** `prefer-active-doc` remains disabled by default — the only Obsidian rule shipped as `off`. Enable it manually for popout window support.
> **Note (v1.13.0):** Settings now open in a new window. `activeDocument` from settings callbacks points to the settings window, not the main vault. Use `this.app.workspace.containerEl.ownerDocument` to target main workspace UI.
> **Note:** `activeDocument`/`activeWindow` are dynamic getters that track the focused window. A listener added via `activeDocument.addEventListener()` at setup cannot reliably be removed via `activeDocument.removeEventListener()` at cleanup. Prefer `registerDomEvent()` (rule 6a), which captures the target at registration.
### 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 `<link>` or `<style>` elements; assign styles via JavaScript |
| 34a | `!important` | Increase selector specificity or use CSS variables | Use `!important` — overrides user themes/snippets |
| 34b | `:has` selector | Toggle classes from TypeScript when conditions change | Use `:has` — causes broad selector invalidation and performance issues |
### Security & Compatibility
| # | Rule | ✅ Do | ❌ Don't |
|---|------|--------|----------|
| 35 | DOM creation | Use Obsidian DOM helpers (`createEl()`, `createDiv()`, `createSpan()`, `createSvg()`, `createFragment()`) via `prefer-create-el`; linter autofixes `activeDocument.createElement()` → `activeWindow.createEl()` (v0.4.1) | Use `document.createElement()`, `document.createDocumentFragment()`, etc. |From gapmiss/obsidian-plugin-skill, MIT.
Compare and alternatives
Featured in
Signals
- Stars
- 194
- Forks
- 15
- Open issues
- 0
- Last commit (this folder)
- Oct 11, 2026 (16 days ago)
From GitHub (fetched Oct 24, 2026).
Details
- Version
- 1.11.1
- License
- MIT
- Repository
- github.com/gapmiss/obsidian-plugin-skill
- First seen
- Oct 24, 2026
- Last checked
- Oct 24, 2026
- Source
- GitHubgithub-graphql
Cite this page
obsidian skill. Agent Mart. https://agentmart.dev/skills/gapmiss-obsidian (last updated 2026-10-24)