Obsidian Plugin Development - Agent Skill
A comprehensive agent skill for developing high-quality Obsidian plugins that follow best practices, pass code review, and adhere to official submission guidelines.
Overview
This skill provides your coding agent with deep knowledge of Obsidian plugin development standards, including:
- ESLint rules from
eslint-plugin-obsidianmd v0.5.1
- Declarative settings (
getSettingDefinitions()), the Obsidian 1.13 settings API
- Official Plugin Guidelines from Obsidian documentation
- Submission via community.obsidian.md and Scorecard optimization
- Memory management and lifecycle best practices
- Security guidelines and XSS prevention
- Platform compatibility (including iOS considerations)
- Network request best practices (requestUrl vs fetch)
Prerequisites
- A coding agent supporting the Agent Skills standard (Claude Code, OpenAI Codex, or Windsurf)
- An Obsidian plugin project (or starting a new one)
Installation
Quick Install
npx skills add https://github.com/gapmiss/obsidian-plugin-skill --skill obsidian
Setup
Option 1: Installer Script (Recommended)
-
Clone this repository:
git clone https://github.com/gapmiss/obsidian-plugin-skill.git
cd obsidian-plugin-skill
-
Run the installer:
./install-skill.sh
-
Select your provider(s):
- All providers — installs to both
.agents/skills/ and .claude/skills/
- Claude Code — installs to
.claude/skills/ with slash commands
- Codex (OpenAI) — installs to
.agents/skills/
- Windsurf — installs to
.agents/skills/
-
Choose installation target (current directory or custom path)
Option 2: Manual Install
Claude Code
git clone https://github.com/gapmiss/obsidian-plugin-skill.git
cd obsidian-plugin-skill
# Copy skill
mkdir -p your-project/.claude/skills/obsidian
cp -r .agents/skills/obsidian/* your-project/.claude/skills/obsidian/
# Copy slash commands
mkdir -p your-project/.claude/commands
cp .claude/commands/obsidian.md your-project/.claude/commands/
cp .claude/commands/create-plugin.md your-project/.claude/commands/
Codex (OpenAI) / Windsurf
git clone https://github.com/gapmiss/obsidian-plugin-skill.git
cd obsidian-plugin-skill
# Copy skill
mkdir -p your-project/.agents/skills/obsidian
cp -r .agents/skills/obsidian/* your-project/.agents/skills/obsidian/
Option 3: Use as Standalone
Just open this directory with your coding agent — no installation needed!
Skill Structure
The skill uses progressive disclosure for optimal performance:
.agents/skills/obsidian/
├── SKILL.md # Main overview (~325 lines)
└── reference/ # Detailed documentation
├── memory-management.md # Lifecycle & cleanup patterns
├── type-safety.md # Type narrowing & safety
├── ui-ux.md # UI standards & commands
├── file-operations.md # Vault & file API
├── css-styling.md # Theming & styling
├── accessibility.md # A11y requirements (MANDATORY)
├── code-quality.md # Best practices & security
├── submission.md # Publishing guidelines
├── community-scanner.md # Scanner behavior & Scorecard (version-stamped)
└── eslint-setup.md # Complete ESLint config guide
SKILL.md provides a concise overview with ESLint rules and Scorecard guidance, while reference files contain comprehensive details on specific topics.
Quick Start: Creating a New Plugin
Interactive Boilerplate Generator
The fastest way to start a new Obsidian plugin with all best practices built-in:
node /path/to/obsidian-plugin-skill/tools/create-plugin.js
Features:
- Generates clean TypeScript boilerplate with no sample code
- Creates
src/ directory structure with main.ts and settings.ts
- Settings tab uses the declarative API (
getSettingDefinitions()), so it's lint-clean and searchable on Obsidian 1.13+. To target older Obsidian, lower minAppVersion and add a display() fallback — see Path B
- Validates plugin metadata in real-time against Obsidian's submission bot rules
- Prompts for target directory to avoid overwriting existing files
- Detects existing projects and only adds missing files
- All generated code follows the skill's best practices automatically
package.json overrides minimatch to ^11.0.5, so npm audit reports 0 vulnerabilities — see below
What it creates:
your-plugin/
├── src/
│ ├── main.ts # Plugin class with settings integration
│ └── settings.ts # Settings interface, defaults, and declarative tab
├── manifest.json # Validated plugin metadata
├── styles.css # CSS with Obsidian variables
├── tsconfig.json # TypeScript configuration
├── package.json # Dependencies, plus a temporary minimatch override
├── esbuild.config.mjs # Build configuration
├── eslint.config.mjs # ESLint configuration
├── version-bump.mjs # Version management script
├── versions.json # Version tracking
├── .gitignore # Git ignore rules
└── LICENSE # MIT license
Interactive prompts:
- Target directory (default: current directory)
- Plugin name (validates: no "Obsidian" or parts of it, no "Plugin")
- Plugin ID (validates: no "obsidian", can't end with "plugin", lowercase only)
- Description (validates: no "Obsidian", no self-reference, 10–250 chars, ends with
., !, or ?, doesn't start with the name)
- Author name
- GitHub username (optional, auto-generates authorUrl)
- Minimum Obsidian version (default
1.13.0)
Real-time validation catches common mistakes:
❌ Validation Errors:
• Plugin ID cannot contain "obsidian"
• Plugin name cannot contain "Plugin"
• Description must end with punctuation: . ! or ?
minimatch override
minimatch before 11.0.1 has a denial-of-service advisory (GHSA-g3hv-75wg-m249). The lint toolchain still depends on vulnerable versions — typescript-eslint on minimatch ^10, and eslint-plugin-obsidianmd via @microsoft/eslint-plugin-sdl → eslint-plugin-n on ^9 — so a plain install reports about 10 moderate vulnerabilities. The generated package.json forces the patched version:
"overrides": {
"minimatch@>=7 <11.0.1": "^11.0.5"
}
It is dev-only (nothing reaches main.js), but the community scanner's Review lists "No known vulnerable dependencies". Linting and type-aware rules are verified to work with it. A "//" key in the generated package.json (npm ignores it) explains the override in the project itself. Remove both once upstream moves to minimatch 11 — see the troubleshooting entry for how to check. Don't run npm audit fix --force: it downgrades eslint-plugin-obsidianmd to 0.0.2.
The remaining install warning, npm warn deprecated eslint@9.39.5, is expected: ESLint 10 currently adds peer-dependency warnings from bundled plugins instead.
Usage
Invoking the Skill
| Provider | Load skill | Create plugin |
|---|
| Claude Code | /obsidian | /create-plugin |
| Codex (OpenAI) | $obsidian | — |
| Windsurf | @obsidian | — |
Skills are automatically discovered by your agent when present in the project directory. You can also invoke them explicitly using the commands above.
Just ask your agent naturally:
Help me implement a new command for my Obsidian plugin
Your agent will automatically use the Obsidian skill guidelines while helping you write code.
What the Skill Helps With
Code Quality
- Prevents common memory leaks
- Enforces type safety (no unsafe casts)
- Ensures proper resource cleanup
- Follows Obsidian's API patterns
UI/UX Standards
- Enforces sentence case for all UI text
- Prevents redundant naming patterns
- Ensures consistent settings UI
Accessibility (A11y)
- MANDATORY keyboard navigation for all interactive elements
- MANDATORY ARIA labels for icon buttons and controls
- MANDATORY focus indicators with proper CSS styling
- Touch target size requirements (44×44px minimum)
- Screen reader support and announcements
- Tooltip positioning with
data-tooltip-position
Security
- Prevents XSS vulnerabilities (no innerHTML/outerHTML)
- Validates manifest structure
- Ensures proper path handling
Platform Compatibility
- iOS compatibility checks (no regex lookbehind)
- Cross-platform path handling
- Mobile-friendly API usage
Submission Ready
- Removes template/sample code
- Validates manifest.json
- Ensures LICENSE compliance
- Follows submission requirements
What's Covered
Most Critical Rules (eslint-plugin-obsidianmd v0.5.1)
The main SKILL.md file highlights the most important rules organized by category:
Submission & Naming:
- Plugin ID: no "obsidian", can't end with "plugin" (checked with
validate-manifest's checkId: true — turn it on before first submission)
- Plugin name: no "Obsidian", no "Plugin"
- Plugin name: no parts of "Obsidian" ("obsi", "sidian", or a leading "ob")
- Description: no "Obsidian", "This plugin", "A plugin that", etc.; doesn't start with the name
- Description: 10–250 characters, ends with
., !, or ?
Memory & Lifecycle:
6. Use registerEvent() for automatic cleanup
7. Don't store view references in plugin
8. Don't call detachLeavesOfType() in onunload
Type Safety:
9. Use instanceof instead of type casting for TFile/TFolder
10. Use .instanceOf(T) for cross-window DOM checks, and e.targetNode?.instanceOf(T) for event targets
UI/UX:
11. Use sentence case for all UI text (ui/sentence-case is off by default since v0.5.0 — opt in)
12. Sentence case in locale JSON files
13. Sentence case in TS/JS locale modules
14. No "command" as a redundant label in command names/IDs
15. No plugin ID/name in command IDs/names
16. No default hotkeys
17. Use .setHeading() for settings headings
Declarative Settings (Obsidian 1.13+):
- Keep
display() while minAppVersion < 1.13.0 (settings-tab/require-display)
- Implement
getSettingDefinitions() so settings appear in global search (settings-tab/prefer-setting-definitions)
- Refresh with
this.update(), not this.display() (settings-tab/prefer-update-over-display)
- Drop
display() once minAppVersion >= 1.13.0 and definitions exist (settings-tab/no-deprecated-display)
API Best Practices:
18. Use Editor API for active file edits
19. Use Vault.process() for background file mods
20. Use FileManager.trashFile() for file deletion
21. Use Vault.getAbstractFileByPath() instead of iterating files
22. Use normalizePath() for user paths
23. Use Platform API for OS detection
24. Use requestUrl() instead of fetch(); parse response.text and cast once at the API boundary (response.json is any)
25. No console.log in onload/onunload in production
26. Use built-in AbstractInputSuggest; use built-in ConfirmationModal (1.13.0+) and FileManager.promptForDeletion() instead of custom confirm modals
27. Check minAppVersion for API compatibility
Popout Window Compatibility:
28. Use activeDocument/activeWindow instead of globals (prefer-active-doc, warn since v0.5.0); apply window-wide <body> classes and styles in every window's document
29. Use window.setTimeout() for timers — not bare setTimeout() or activeWindow.setTimeout() (prefer-window-timers)
Event Handling: