Get the FREE Ultimate OpenClaw Setup Guide →

obsidian

npx machina-cli add skill gapmiss/obsidian-plugin-skill/obsidian --openclaw
Files (1)
SKILL.md
10.4 KB

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.js in the skill repository
  • Slash command: /create-plugin for guided setup
  • 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.1.9)

Submission & Naming

#Rule✅ Do❌ Don't
1Plugin IDOmit "obsidian"; don't end with "plugin"Include "obsidian" or end with "plugin"
2Plugin nameOmit "Obsidian"; don't end with "Plugin"Include "Obsidian" or end with "Plugin"
3Plugin nameDon't start with "Obsi" or end with "dian"Start with "Obsi" or end with "dian"
4DescriptionOmit "Obsidian", "This plugin", etc.Use "Obsidian" or "This plugin"
5DescriptionEnd with .?!) punctuationLeave description without terminal punctuation

Memory & Lifecycle

#Rule✅ Do❌ Don't
6Event cleanupUse registerEvent() for automatic cleanupRegister events without cleanup
7View referencesReturn views/components directlyStore view references in plugin properties or pass plugin as component to MarkdownRenderer

Type Safety

#Rule✅ Do❌ Don't
8TFile/TFolderUse instanceof for type checkingCast to TFile/TFolder; use any; use var

UI/UX

#Rule✅ Do❌ Don't
9UI textSentence case — "Advanced settings"Title Case — "Advanced Settings"
10JSON localeSentence case in JSON locale files (recommendedWithLocalesEn)Title case in locale JSON
11TS/JS localeSentence case in TS/JS locale modulesTitle case in locale modules
12Command namesOmit "command" in command names/IDsInclude "command" in names/IDs
13Command IDsOmit plugin ID/name from command IDs/namesDuplicate plugin ID in command IDs
14HotkeysNo default hotkeysSet default hotkeys
15Settings headingsUse .setHeading()Create manual HTML headings; use "General", "settings", or plugin name in headings

API Best Practices

#Rule✅ Do❌ Don't
16Active file editsUse Editor APIUse Vault.modify() for active file edits
17Background file modsUse Vault.process()Use Vault.modify() for background modifications
18User pathsUse normalizePath()Hardcode .obsidian path; use raw user paths
19OS detectionUse Platform APIUse navigator.platform/userAgent
20Network requestsUse requestUrl()Use fetch()
21LoggingMinimize console logging; none in onload/onunload in productionUse console.log in onload/onunload

Styling

#Rule✅ Do❌ Don't
22CSS variablesUse Obsidian CSS variables for all stylingHardcode colors, sizes, or spacing
23CSS scopeScope CSS to plugin containersUse broad CSS selectors
24Style elementsUse styles.css file (no-forbidden-elements)Create <link> or <style> elements; assign styles via JavaScript

Accessibility (MANDATORY)

#Rule✅ Do❌ Don't
25Keyboard accessMake all interactive elements keyboard accessible; Tab through all elementsCreate inaccessible interactive elements
26ARIA labelsProvide ARIA labels for icon buttons; use data-tooltip-position for tooltipsUse icon buttons without ARIA labels
27Focus indicatorsUse :focus-visible with Obsidian CSS variables; touch targets ≥ 44×44pxRemove focus indicators; make touch targets < 44×44px

Security & Compatibility

Rule✅ Do❌ Don't
DOM safetyUse Obsidian DOM helpers (createDiv(), createSpan(), createEl())Use innerHTML/outerHTML or document.createElement
iOS compatAvoid regex lookbehind (iOS < 16.4 incompatibility)Use regex lookbehind

Code Quality

Rule✅ Do❌ Don't
Sample codeRemove all sample/template codeKeep class names like MyPlugin, SampleModal
Object.assignObject.assign({}, defaults, overrides) (object-assign)Object.assign(defaultsVar, other) — mutates defaults
LICENSECopyright holder must not be "Dynalist Inc."; year must be current (validate-license)Leave "Dynalist Inc." as holder or use an outdated year
AsyncUse async/awaitUse Promise chains

Detailed Guidelines

For comprehensive information on specific topics, see the reference files:

Memory Management & Lifecycle

  • Using registerEvent(), addCommand(), registerDomEvent(), registerInterval()
  • Avoiding view references in plugin
  • Not using plugin as component
  • Proper leaf cleanup

Type Safety

  • Using instanceof instead of type casting
  • Avoiding any type
  • Using const and let over var

UI/UX Standards

  • Sentence case enforcement (TypeScript, JSON locale, TS/JS locale modules)
  • recommendedWithLocalesEn config for locale file checks
  • Command naming conventions (no "command", no plugin name, no plugin ID)
  • Settings and configuration best practices

File & Vault Operations

  • View access patterns
  • Editor vs Vault API
  • Atomic file operations
  • File management
  • Path handling

CSS Styling Best Practices

  • Avoiding inline styles
  • Using Obsidian CSS variables
  • Scoping plugin styles
  • Theme support
  • Spacing and layout

Accessibility (A11y)

  • Keyboard navigation (MANDATORY)
  • ARIA labels and roles (MANDATORY)
  • Tooltips and accessibility
  • Focus management (MANDATORY)
  • Focus visible styles (MANDATORY)
  • Screen reader support (MANDATORY)
  • Mobile and touch accessibility (MANDATORY)
  • Accessibility checklist

Code Quality & Best Practices

  • Removing sample code
  • Security best practices
  • Platform compatibility
  • API usage best practices
  • Async/await patterns
  • DOM helpers

Plugin Submission Requirements

  • Repository structure
  • Submission process
  • Semantic versioning
  • Testing checklist
  • Additional resources and important notes

Plugin Submission Validation Workflow

Before submitting a plugin, follow this sequence:

  1. Run ESLintnpx eslint . using eslint-plugin-obsidianmd; fix all errors (warnings are informational)
  2. Validate manifest — Confirm id, name, description, version, and minAppVersion meet naming and formatting rules (rules 1–5)
  3. Check LICENSE — Copyright holder must not be "Dynalist Inc." and the year must be current
  4. Test on mobile — Verify no regex lookbehind, no fetch(), and touch targets ≥ 44×44px (skip only if plugin is declared desktop-only)
  5. Keyboard accessibility audit — Tab through all interactive elements; confirm focus indicators and ARIA labels are present
  6. Submit — Open a PR to the community plugins repository with the updated manifest.json and community-plugins.json entry

If ESLint reports new errors after fixing, re-run from step 1.


When Reviewing/Writing Code

Use this checklist for code review and implementation:

  1. Memory management: Are components and views properly managed?
  2. Type safety: Using instanceof instead of casts?
  3. UI text: Is everything in sentence case?
  4. Command naming: No redundant words?
  5. File operations: Using preferred APIs?
  6. Mobile compatibility: No iOS-incompatible features?
  7. Sample code: Removed all boilerplate?
  8. Manifest: Correct version, valid structure?
  9. Accessibility: Keyboard navigation, ARIA labels, focus indicators?
  10. Testing: Can you use the plugin without a mouse?
  11. Touch targets: Are all interactive elements at least 44×44px?
  12. Focus styles: Using :focus-visible and proper CSS variables?

Common Patterns

Proper Command Registration

// ✅ CORRECT
this.addCommand({
  id: 'insert-timestamp',
  name: 'Insert timestamp',
  editorCallback: (editor: Editor, view: MarkdownView) => {
    editor.replaceSelection(new Date().toISOString());
  }
});

Safe Type Narrowing

// ✅ 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);
}

Keyboard Accessible Button

// ✅ 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();
  }
});

Themed CSS

/* ✅ 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;
}

When 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.

Source

git clone https://github.com/gapmiss/obsidian-plugin-skill/blob/main/.claude/skills/obsidian/SKILL.mdView on GitHub

Overview

These guidelines provide a comprehensive framework for Obsidian.md plugin development. They cover the 27 ESLint rules from eslint-plugin-obsidianmd v0.1.9, TypeScript best practices, memory management, and proper API usage. They also emphasize UI/UX standards, locale sentence-case enforcement, and submission requirements to accelerate safe, maintainable plugins.

How This Skill Works

The skill maps each rule to concrete code patterns and project setup steps, guiding developers through development, review, and submission workflows. It references core Obsidian APIs and project artifacts like main.ts, manifest.json, Plugin class, MarkdownView, TFile, and vault operations, ensuring memory-safe lifecycles and correct API usage (e.g., requestUrl vs fetch). It also provides quick-start guidance and practical examples to keep plugins robust and user-friendly.

When to Use It

  • Starting a new Obsidian plugin project and generating boilerplate with the quick-start tool.
  • Refactoring main.ts or Plugin class to align with eslint-plugin-obsidianmd v0.1.9 rules.
  • Implementing API calls and vault operations while following requestUrl vs fetch guidance.
  • Adding or enforcing sentence-case locale keys and UI strings.
  • Preparing your plugin for submission by validating naming, descriptions, and event cleanup.

Quick Start

  1. Step 1: Run the quick-start boilerplate generator (tools/create-plugin.js or /create-plugin).
  2. Step 2: Implement your plugin in main.ts, set up manifest.json, and extend the Plugin class.
  3. Step 3: Run ESLint with eslint-plugin-obsidianmd v0.1.9 and fix violations until clean.

Best Practices

  • Use registerEvent() for automatic cleanup instead of leaving event listeners unmanaged.
  • Prefer instanceof checks for TFile/TFolder rather than unsafe casts.
  • Always use normalizePath() for user-facing paths; avoid hardcoding .obsidian or raw user paths.
  • Favor the Editor API for active-file edits and Vault API for broader modifications.
  • Minimize console logging; avoid logging in onload/onunload in production, and follow UI/locale conventions.

Example Use Cases

  • A boilerplate Obsidian plugin project generated by the quick-start tool.
  • A main.ts implementing a Plugin class with proper registerEvent-based cleanup.
  • Network calls implemented with requestUrl() instead of fetch().
  • Locale JSON files using sentence-case keys and values to meet UI/locale standards.
  • Commands and IDs defined without including the plugin name or the word 'command' in IDs.

Frequently Asked Questions

Add this skill to your agents
Sponsor this space

Reach thousands of developers