Comprehensive guidelines for Obsidian.md plugin development including all 27 ESLint rules from eslint-plugin-obsidianmd v0.1.9, TypeScript best practices, memory management, API usage (requestUrl vs fetch), UI/UX standards, locale file sentence-case enforcement, 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.
Install with Tessl CLI
npx tessl i github:gapmiss/obsidian-plugin-skill --skill obsidian87
Does it follow best practices?
If you maintain this skill, you can automatically optimize it using the tessl CLI to improve its score:
npx tessl skill review --optimize ./path/to/skillValidation for skill structure
You are assisting with Obsidian plugin development. Follow these comprehensive guidelines derived from the official Obsidian ESLint plugin rules, submission requirements, and best practices.
For new plugin projects, an interactive boilerplate generator is available:
tools/create-plugin.js in the skill repository/create-plugin for guided setupRecommend the boilerplate generator when users:
Submission & Naming:
.?!) punctuation - Validation bot enforcedMemory & Lifecycle:
6. Use registerEvent() for automatic cleanup - Prevents memory leaks
7. Don't store view references in plugin - Causes memory leaks
Type Safety:
8. Use instanceof instead of type casting - Type safety for TFile/TFolder
UI/UX:
9. Use sentence case for all UI text - "Advanced settings" not "Advanced Settings"
10. Enforce sentence case in JSON locale files - ui/sentence-case-json (use recommendedWithLocalesEn)
11. Enforce sentence case in TS/JS locale modules - ui/sentence-case-locale-module
12. No "command" in command names/IDs - Redundant
13. No plugin ID/name in command IDs/names - Obsidian auto-namespaces
14. No default hotkeys - Avoid conflicts
15. Use .setHeading() for settings headings - Not manual HTML
API Best Practices:
16. Use Editor API for active file edits - Preserves cursor position
17. Use Vault.process() for background file mods - Prevents conflicts
18. Use normalizePath() for user paths - Cross-platform compatibility
19. Use Platform API for OS detection - Not navigator
20. Use requestUrl() instead of fetch() - Bypasses CORS restrictions
21. No console.log in onload/onunload in production - Pollutes console
Styling:
22. Use Obsidian CSS variables - Respects user themes
23. Scope CSS to plugin containers - Prevents style conflicts
24. Don't create <link> or <style> elements - Use styles.css file instead (no-forbidden-elements)
Accessibility (MANDATORY):
25. Make all interactive elements keyboard accessible - Accessibility required
26. Provide ARIA labels for icon buttons - Accessibility required
27. Define clear focus indicators - Use :focus-visible
Security & Compatibility:
28. Don't use innerHTML/outerHTML - Security risk (XSS)
29. Avoid regex lookbehind - iOS < 16.4 incompatibility
Code Quality:
30. Remove all sample/template code - MyPlugin, SampleModal, etc.
31. Don't mutate defaults with Object.assign - Use Object.assign({}, defaults, overrides) (object-assign)
32. Validate LICENSE copyright holder and year - Must not be "Dynalist Inc.", year must be current (validate-license)
For comprehensive information on specific topics, see the reference files:
registerEvent(), addCommand(), registerDomEvent(), registerInterval()instanceof instead of type castingany typeconst and let over varrecommendedWithLocalesEn config for locale file checksMemory & Lifecycle:
registerEvent(), addCommand(), registerDomEvent(), registerInterval()Type Safety:
instanceof for type checking (not type casting)unknown instead of anyconst and let (not var)API Usage:
this.app (not global app)Vault.process() for background file modificationsFileManager.processFrontMatter() for YAMLfileManager.trashFile() for deletionsnormalizePath() for user-defined pathsPlatform API for OS detectionAbstractInputSuggest for autocompleterequestUrl() instead of fetch() for network requestsUI/UX:
recommendedWithLocalesEn).setHeading() for settings headingscreateDiv(), createSpan(), createEl())window.setTimeout/setInterval with number typeStyling:
Accessibility (MANDATORY):
:focus-visibledata-tooltip-position for tooltipsCode Quality:
Memory & Lifecycle:
onunload()Type Safety:
instanceof)any typevarAPI Usage:
app objectVault.modify() for active file edits.obsidian path (use vault.configDir)navigator.platform/userAgent (use Platform API)fetch() (use requestUrl() instead)UI/UX:
.setHeading())Styling:
<link> or <style> elements (use styles.css file)Security & Compatibility:
innerHTML/outerHTML (XSS risk)Accessibility:
Code Quality:
document.createElement (use Obsidian helpers)Object.assign(defaultsVar, other) — mutates defaults; use Object.assign({}, defaults, other) insteadUse this checklist for code review and implementation:
instanceof instead of casts?:focus-visible and proper CSS variables?// ✅ CORRECT
this.addCommand({
id: 'insert-timestamp',
name: 'Insert timestamp',
editorCallback: (editor: Editor, view: MarkdownView) => {
editor.replaceSelection(new Date().toISOString());
}
});// ✅ CORRECT
const file = this.app.vault.getAbstractFileByPath(path);
if (file instanceof TFile) {
// TypeScript now knows it's a TFile
await this.app.vault.read(file);
}// ✅ CORRECT
const button = containerEl.createEl('button', {
attr: {
'aria-label': 'Open settings',
'data-tooltip-position': 'top'
}
});
button.setText('⚙️');
button.addEventListener('keydown', (e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
performAction();
}
});/* ✅ CORRECT */
.my-plugin-modal {
background: var(--modal-background);
color: var(--text-normal);
padding: var(--size-4-4);
border-radius: var(--radius-m);
font-size: var(--font-ui-medium);
}
.my-plugin-button:focus-visible {
outline: 2px solid var(--interactive-accent);
outline-offset: 2px;
}eslint-plugin-obsidianmd (install for automatic checking)eslint-plugin-obsidianmd v0.1.9--fix flagWhen helping with Obsidian plugin development, proactively apply these rules and suggest improvements based on these guidelines. Refer to the detailed reference files for comprehensive information on specific topics.
040c26e
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.