obsidian
Provides development guidelines for creating Obsidian plugins, including boilerplate generation and API best practices.
Install
mkdir -p .claude/skills/obsidian && curl -L -o skill.zip "https://agentskills.codes/api/skills/download/248" && unzip -o skill.zip -d .claude/skills/obsidian && rm skill.zipInstalls to .claude/skills/obsidian
Activation
This is the description your AI agent reads to decide when to run this skill — the better it matches your request, the more reliably it fires.
Comprehensive guidelines for Obsidian.md plugin development including ESLint rules from eslint-plugin-obsidianmd v0.4.1, 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.Key capabilities
- →Generate new Obsidian plugin project boilerplate
- →Validate plugin IDs and names against community rules
- →Manage event listeners for automatic cleanup
- →Use Editor API for active file edits
- →Check `minAppVersion` for API availability
- →Ensure popout window compatibility with `activeWindow`
How it works
The skill provides guidelines derived from official Obsidian ESLint plugin rules and submission requirements. It offers an interactive boilerplate generator for new plugin projects.
Inputs & outputs
When to use obsidian
- →Generate a new Obsidian plugin project boilerplate
- →Validate plugin naming against community rules
- →Optimize plugin code for memory and performance
- →Prepare plugin for submission to community.obsidian.md
About this skill
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.jsin the skill repository - Command: Invoke
create-pluginusing 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.1)
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" | 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-caseis now enabled (warn) and enforced on inline UI strings — it was disabled in v0.3.0. Use therecommendedWithLocalesEnconfig 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 PluginSettingTabonly.extends obsidian.PluginSettingTabis 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() |
| 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 (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 activeWindow.setTimeout(), setInterval(), etc. | Use bare setTimeout(), setInterval() |
| 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-docremains disabled by default — the only Obsidian rule shipped asoff. Enable it manually for popout window support.
Note (v1.13.0): Settings now open in a new window.
activeDocumentfrom settings callbacks points to the settings window, not the main vault. Usethis.app.workspace.containerEl.ownerDocumentto target main workspace UI.
Note:
activeDocument/activeWindoware dynamic getters that track the focused window. A listener added viaactiveDocument.addEventListener()at setup cannot reliably be removed viaactiveDocument.removeEventListener()at cleanup. PreferregisterDomEvent()(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. |
| 36 | Node.js modules | Guard Node.js imports with Platform.isDesktop check (no-nodejs-modules) | Import Node.js modules without platform guard |
| 37 | iOS compat | Avoid regex lookbehind (iOS < 16.4 incompatibility) | Use regex lookbehind |
Accessibility (MANDATORY)
| # | Rule | ✅ Do | ❌ Don't |
|---|---|---|---|
| 38 | Keyboard access | Make all interactive elements keyboard accessible; Tab through all elements | Create inaccessible interactive elements |
| 39 | ARIA labels | Provide ARIA labels for icon buttons; use data-tooltip-position for tooltips | Use icon buttons without ARIA labels |
| 40 | Focus indicators | Use :focus-visible with Obsidian CSS variables; touch targets ≥ 44×44px | Remove focus indicators; make touch targets < 44×44px |
Code Quality
| Rule | ✅ Do | ❌ Don't |
|---|---|---|
| Sample code | Remove all sample/template code | Keep class names like MyPlugin, SampleModal |
| Object.assign | Object.assign({}, defaults, overrides) (object-assign) | Object.assign(defaultsVar, other) — mutates defaults |
| LICENSE | Copyright holder must not be "Dynalist Inc."; year must be current (validate-license) | Leave "Dynalist Inc." as holder or use an outdated year |
| Async | Use async/await | Use Promise chains |
| Deprecated packages | Replace flagged npm packages with Node.js built-ins (e.g., |
Content truncated.
When not to use it
- →When developing plugins for platforms other than Obsidian.md
- →When the project does not involve Obsidian API development
Limitations
- →Guidelines are specific to Obsidian.md plugin development
- →Rules are based on eslint-plugin-obsidianmd v0.4.1
How it compares
This skill provides specific, documented rules and a generator for Obsidian plugin development, unlike a manual approach that would require consulting multiple external documents.
Compared to similar skills
obsidian side by side with the closest alternatives in the catalog.
| Skill | Installs | Updated | Safety | Difficulty |
|---|---|---|---|---|
| obsidian (this skill) | 29 | 27d | No flags | Intermediate |
| cursor-rules-config | 4 | 27d | Review | Intermediate |
| drizzle | 238 | 2mo | No flags | Intermediate |
| zustand | 113 | 2mo | No flags | Intermediate |
Try saying
Example prompts that trigger this skill in your AI assistant.
You might also like
cursor-rules-config
jeremylongshore
Configure .cursorrules for project-specific AI behavior. Triggers on "cursorrules", ".cursorrules", "cursor rules", "cursor config", "cursor project settings". Use when configuring systems or services. Trigger with phrases like "cursor rules config", "cursor config", "cursor".
drizzle
lobehub
Drizzle ORM schema and database guide. Use when working with database schemas (src/database/schemas/*), defining tables, creating migrations, or database model code. Triggers on Drizzle schema definition, database migrations, or ORM usage questions.
zustand
lobehub
Zustand state management guide. Use when working with store code (src/store/**), implementing actions, managing state, or creating slices. Triggers on Zustand store development, state management questions, or action implementation.
motion-canvas
davila7
Complete production-ready guide for Motion Canvas with ESM/CommonJS workarounds, full setup templates, and troubleshooting for programmatic video creation using TypeScript
turborepo
vercel
Turborepo monorepo build system guidance. Triggers on: turbo.json, task pipelines, dependsOn, caching, remote cache, the "turbo" CLI, --filter, --affected, CI optimization, environment variables, internal packages, monorepo structure/best practices, and boundaries. Use when user: configures tasks/workflows/pipelines, creates packages, sets up monorepo, shares code between apps, runs changed/affected packages, debugs cache, or has apps/packages directories.
shadcn-ui-setup
maneeshanif
Install and configure Shadcn/ui component library with Radix UI primitives, Aceternity UI effects, set up components, and manage the component registry. Use when adding Shadcn/ui to a Next.js project or installing specific UI components for Phase 2.