plugins/obsidian-development/skills/obsidian-plugin-development/SKILL.md
Ensures compliance with ObsidianReviewBot automated checks, eslint-plugin-obsidianmd rules, and official Obsidian plugin guidelines. TRIGGER WHEN: writing, reviewing, or fixing Obsidian community plugin code DO NOT TRIGGER WHEN: the task is outside the specific scope of this component.
npx skillsauth add acaprino/alfio-claude-plugins obsidian-plugin-developmentInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Write Obsidian plugin code that passes the ObsidianReviewBot automated review on first submission. All rules below are enforced by the bot via eslint-plugin-obsidianmd and @typescript-eslint. Violations labeled "Required" block merging.
obsidianmd/obsidian-releasesEvery user-visible string: sentence case only.
// NO
'Block Settings'
'Add Block'
'Recent Files'
// YES
'Block settings'
'Add block'
'Recent files'
Applies to: Setting.setName(), Setting.setDesc(), createEl() text, button labels, modal titles, notices, menu items, tooltips. Proper nouns and acronyms (e.g. "API", "GitHub", "Obsidian") keep their casing.
Never assign element.style.* directly. Use CSS classes.
// NO
el.style.display = 'flex';
el.style.transform = 'scale(0.9)';
el.style.opacity = '0';
// YES -- use CSS classes
el.addClass('hp-flex-container');
el.toggleClass('hp-scaled', true);
el.toggleClass('hp-hidden', true);
// For dynamic CSS custom properties, use setCssProps or setCssStyles:
el.setCssStyles({ '--my-var': value });
Flagged properties include: display, transform, opacity, width, height, margin, padding, cursor, fontSize, fontFamily, flexDirection, alignItems, flexShrink, borderRadius, backdropFilter, background, borderWidth, borderStyle, transition, gridTemplateRows, transformOrigin, and all others.
Don't as Type when it doesn't change the type.
// NO -- assertion is redundant with ?? fallback
draft.url as string ?? ''
draft.showDate as boolean ?? true
// YES
String(draft.url ?? '')
Boolean(draft.showDate ?? true)
// or just
(draft.url ?? '') as string // assertion AFTER coalescing
Every Promise must be: awaited, .catch()ed, .then() with rejection handler, or voided.
// NO
someAsyncFn();
this.app.vault.read(file).then(text => { ... });
// YES
await someAsyncFn();
void someAsyncFn();
this.app.vault.read(file).then(text => { ... }, err => console.error(err));
this.app.vault.read(file).then(text => { ... }).catch(console.error);
Remove async from methods that don't use await.
// NO
async onOpen() { this.render(); }
// YES
onOpen() { this.render(); }
Don't return a Promise in callbacks expecting void.
// NO -- event callback expects void
this.registerEvent(this.app.vault.on('modify', async (file) => {
await this.reload();
}));
// YES
this.registerEvent(this.app.vault.on('modify', (file) => {
void this.reload();
}));
Ensure values won't stringify as [object Object].
// NO -- if draft is Record<string,unknown>, draft.mode could be an object
`Value: ${draft.mode ?? 'default'}`
// YES
`Value: ${String(draft.mode ?? 'default')}`
Don't create HTML headings. Use Setting.setHeading().
// NO
contentEl.createEl('h2', { text: 'My settings' });
// YES
new Setting(contentEl).setName('My settings').setHeading();
Obsidian handles leaf cleanup. Detaching resets user's layout.
// NO
onunload() {
this.app.workspace.detachLeavesOfType(VIEW_TYPE);
}
// YES
onunload() {
// Obsidian cleans up leaves automatically
}
Use instanceof instead of type casting.
// NO
const file = abstractFile as TFile;
// YES
if (abstractFile instanceof TFile) { ... }
Don't create <style> or <link> elements dynamically.
Don't pass this (plugin) to MarkdownRenderer.render(). Use a Component instance.
// NO
MarkdownRenderer.render(this.app, md, el, '', this);
// YES -- use a Component subclass or this view/block
MarkdownRenderer.render(this.app, md, el, '', this.component);
Don't store view references in plugin properties (memory leak).
Don't hardcode .obsidian. Use this.app.vault.configDir.
Use Platform API, not navigator.userAgent.
// NO
if (navigator.userAgent.includes('Mac')) { ... }
// YES
import { Platform } from 'obsidian';
if (Platform.isMacOS) { ... }
Lookbehinds break on some iOS versions. Avoid unless isDesktopOnly: true.
FileManager.trashFile() instead of Vault.trash()/Vault.delete()getAbstractFileByPath()normalizePath() for user-provided pathsRemove MyPlugin, SampleModal, template code from obsidian-sample-plugin.
Don't use Object.assign(this.settings, data) to mutate defaults.
manifest.json must have valid structureLICENSE must have correct copyright holder and current year. ? ! )AbstractInputSuggest instead of copied TextInputSuggest| Practice | Details |
|----------|---------|
| No innerHTML/outerHTML | Use createEl, setText, sanitizeHTMLToDom |
| Use requestUrl() | Instead of fetch() for network requests |
| CSS variables for theming | --background-secondary, --text-muted, etc. |
| Scope CSS | All plugin CSS scoped to plugin containers |
| Accessibility | aria-label on icon buttons, keyboard nav, focus indicators |
| Touch targets | Min 44x44px on mobile |
| Auto-cleanup | registerEvent(), registerInterval(), register() |
| No production logging | No console.log in onload()/onunload() |
See references/obsidian-api-reference.md in this skill directory for a condensed TypeScript API reference covering all key classes: Plugin, App, Vault, Workspace, MetadataCache, FileManager, Component, View, Modal, Setting, Menu, MarkdownRenderer, Platform, DOM helpers, and more.
For the full type definitions, read node_modules/obsidian/obsidian.d.ts in the project.
npm install eslint-plugin-obsidianmd --save-dev
Configure ESLint with the plugin's recommended config. Run before submitting PR.
| Mistake | Fix |
|---------|-----|
| Title Case in UI text | Sentence case everything |
| el.style.display = 'none' | el.addClass('hp-hidden') |
| as string ?? '' | String(x ?? '') |
| createEl('h2', ...) in modal | new Setting(el).setName(...).setHeading() |
| Async onOpen without await | Remove async keyword |
| Unhandled promise | Add void, await, or .catch() |
| detachLeavesOfType in onunload | Remove -- Obsidian handles it |
| abstractFile as TFile | if (x instanceof TFile) |
development
Unified web frontend knowledge base covering CSS architecture, UX psychology, UI components, distinctive aesthetics, and interface design generation. TRIGGER WHEN: working on web styling, design systems, component decisions, responsive strategy, distinctive frontend aesthetics, or exploring multiple interface designs. DO NOT TRIGGER WHEN: the task is purely backend or unrelated to web frontend.
development
Coordinate parallel code reviews across multiple quality dimensions with finding deduplication, severity calibration, and consolidated reporting. Use this skill when organizing multi-reviewer code reviews, calibrating finding severity, or consolidating review results.
tools
Knowledge base for the codebase-mapper plugin. Provides writing guidelines, tone rules, and diagram conventions for generating human-readable project guides. Referenced by all codebase-mapper agents during document generation. TRIGGER WHEN: referenced by codebase-mapper pipeline agents (codebase-explorer, overview-writer, tech-writer, flow-writer, onboarding-writer, ops-writer, config-writer, guide-reviewer) during document generation. DO NOT TRIGGER WHEN: outside the /map-codebase pipeline (general documentation work should use docs:readme-craft or codebase-mapper:docs-create).
tools
Progressive Web App knowledge base for 2025-2026: Web App Manifest, Service Workers (Workbox 7, Serwist), Web Push (VAPID, RFC 8030/8291/8292, Declarative Push for Safari 18.4+), install flows (beforeinstallprompt, Window Controls Overlay), OPFS storage, Project Fugu, Core Web Vitals (INP < 200ms), security (HTTPS, CSP, COOP/COEP), and distribution (Bubblewrap, PWA Builder MSIX, Capacitor). TRIGGER WHEN: building, auditing, or debugging PWAs, including manifest, service worker, Web Push, install flow, OPFS, Background Sync, Wake Lock, vite-plugin-pwa, Next.js Serwist, @angular/pwa, @vite-pwa/nuxt, Bubblewrap, TWA, PWA Builder, or Capacitor wrapping. DO NOT TRIGGER WHEN: the task is generic frontend styling (use frontend), React performance (use react-development:review-react), cross-platform security unrelated to PWA (use platform-engineering), Tauri or Electron wrappers (use tauri-development), or GA4 / analytics (use digital-marketing).