From obsidian-development
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 claudepluginhub acaprino/alfio-claude-plugins --plugin obsidian-developmentThis skill uses the workspace's default tool permissions.
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.
Searches, retrieves, and installs Agent Skills from prompts.chat registry using MCP tools like search_skills and get_skill. Activates for finding skills, browsing catalogs, or extending Claude.
Searches prompts.chat for AI prompt templates by keyword or category, retrieves by ID with variable handling, and improves prompts via AI. Use for discovering or enhancing prompts.
Guides MCP server integration in Claude Code plugins via .mcp.json or plugin.json configs for stdio, SSE, HTTP types, enabling external services as tools.
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 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) |