obsidian について
Obsidian プラグイン開発者向けのガイドラインスキルです。ESLint ルールや申請要件に加え、ライフサイクル、API、UI、互換性、アクセシビリティを扱い、新規プロジェクト用のひな形生成機能も備えています。
主な機能
- 01最小構成のひな形を生成
- 02申請と命名のルールを網羅
- 03ライフサイクルと API の指針
- 04UI、スタイル、アクセシビリティの規範
活用シーン
- 01Obsidian プラグインのプロジェクト開始
- 02プラグインコードや申請手順の確認
- 03設定、ファイル、イベント処理の検討
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ gapmiss/ obsidian-plugin-skill --skill obsidianインストール後、Claude Code で/obsidianを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
reference/accessibility.mdcode-quality.mdcommunity-scanner.mdcss-styling.mdeslint-setup.mdfile-operations.mdmemory-management.mdsubmission.mdtype-safety.mdui-ux.md詳細
Obsidian プラグイン開発ガイドライン
公式の Obsidian ESLint プラグインのルール、提出要件、ベストプラクティスに基づく、包括的なガイドラインに従ってください。
はじめに
クイックスタートツール
新しいプラグインプロジェクト向けに、対話型のボイラープレート生成ツールを利用できます。
- スクリプト:スキルリポジトリ内の
tools/create-plugin.js - コマンド:エージェントで使用する方法(
/create-plugin、$create-plugin、または@create-plugin)でcreate-pluginを呼び出します - サンプルコードを含まない、ベストプラクティスに沿った最小限のボイラープレートを生成します
- 既存のプロジェクトを検出し、不足しているファイルのみを追加します
新しいプラグインの作成方法を尋ねられた場合、新しいプロジェクトを始めたい場合、または基本構造の設定についてサポートが必要な場合は、ボイラープレート生成ツールを推奨してください。
ルールリファレンス(eslint-plugin-obsidianmd v0.4.2)
提出と命名
| # | ルール | ✅ 推奨 | ❌ 非推奨 |
|---|---|---|---|
| 1 | プラグイン ID | 「obsidian」を省略し、「plugin」で終わらせない | 「obsidian」を含める、または「plugin」で終わらせる |
| 2 | プラグイン名 | 「Obsidian」を省略し、「Plugin」で終わらせない | 「Obsidian」を含める、または「Plugin」で終わらせる |
| 3 | プラグイン名 | 「Obsi」で始めず、「dian」で終わらせない | 「Obsi」で始める、または「dian」で終わらせる |
| 4 | 説明 | 「Obsidian」、「This plugin」などを省略する | 「Obsidian」や「This plugin」を使う |
| 5 | 説明 | .?!) の句読点で終える | 説明文を句読点なしで終える |
メモリとライフサイクル
| # | ルール | ✅ 推奨 | ❌ 非推奨 |
|---|---|---|---|
| 6 | イベントのクリーンアップ | 自動クリーンアップには registerEvent() を使用する | クリーンアップせずにイベントを登録する |
| 6a | DOM イベント | プラグインまたは所有するコンポーネントで registerDomEvent() を使用する | addEventListener と手動の removeEventListener によるクリーンアップを組み合わせる |
| 7 | ビューの参照 | ビュー/コンポーネントを直接返す | ビューの参照をプラグインのプロパティに保存する、または MarkdownRenderer のコンポーネントとしてプラグインを渡す |
| 8 | リーフの切り離し | リーフのクリーンアップは Obsidian に任せる | onunload で detachLeavesOfType() を呼び出す |
型安全性
| # | ルール | ✅ 推奨 | ❌ 非推奨 |
|---|---|---|---|
| 9 | TFile/TFolder | 型チェックには instanceof を使用する | TFile/TFolder にキャストする、any を使用する、var を使用する |
| 10 | DOM instanceof | DOM Node/UIEvent には .instanceOf(T) を使用する | ウィンドウをまたぐ DOM チェックに instanceof を使用する |
UI/UX
| # | ルール | ✅ 推奨 | ❌ 非推奨 |
|---|---|---|---|
| 11 | UI テキスト | 文頭のみ大文字にする — 「Advanced settings」。固有名詞には acronyms/brands オプションを使用する | 単語の先頭を大文字にする — 「Advanced Settings」 |
| 12 | JSON ロケール | JSON ロケールファイルでは文頭のみ大文字にする(recommendedWithLocalesEn) | ロケール JSON で単語の先頭を大文字にする |
| 13 | TS/JS ロケール | TS/JS ロケールモジュールでは文頭のみ大文字にする | ロケールモジュールで単語の先頭を大文字にする |
注記(v0.4.0):
ui/sentence-caseが有効になり(warn)、インライン UI 文字列に適用されるようになりました。v0.3.0 では無効でした。英語のロケールファイルもチェックするには、recommendedWithLocalesEn設定を使用してください(ルール 12~13)。 | 14 | コマンド名 | コマンド名/IDでは「command」を省略する | 名前/IDに「command」を含める | | 15 | コマンド ID | コマンド ID/名前からプラグイン ID/名前を省略する | コマンド ID にプラグイン ID を重複して含める | | 16 | ホットキー | デフォルトのホットキーを設定しない | デフォルトのホットキーを設定する | | 17 | 設定の見出し |.setHeading()を使用する | 手動で HTML の見出しを作成する、見出しに「General」、「settings」、またはプラグイン名を使用する |
宣言型設定(1.13.0+)
4 つの settings-tab ルールはすべて、recommended では warn として提供されます。ルール 17a/17c/17d は manifest.json の minAppVersion を読み取ります。17b はバージョンによる制限がありません。
| # | ルール | ✅ 推奨 | ❌ 非推奨 |
|---|---|---|---|
| 17a | settings-tab/require-display | minAppVersion < 1.13.0 の場合は display() を残します | 古い Obsidian で何も表示されない宣言型設定だけを提供します |
| 17b | settings-tab/prefer-setting-definitions | すべての PluginSettingTab に getSettingDefinitions() を実装します | display() のみに依存します。1.13 以降のグローバル検索に設定が表示されません |
| 17c | settings-tab/prefer-update-over-display | 宣言型設定を再描画するには this.update() を呼び出します | this.display() を呼び出します。定義が空でない場合は実行されません |
| 17d | settings-tab/no-deprecated-display | minAppVersion >= 1.13.0 で定義が存在する場合は、display() を削除します | 使われない display() を残します(自動修正可能) |
| — | 設定データ | 永続化されるデータはすべて plugin.settings 内に保持します | saveData() で兄弟キーを保存します。自動永続化によって上書きされます |
検出上の注意点: これらのルールが一致するのは、
extends PluginSettingTabのみです。extends obsidian.PluginSettingTabは対象外で、警告されません。ただし、基本的なガイダンスは引き続き適用されます。
API のベストプラクティス
| # | ルール | ✅ 推奨 | ❌ 非推奨 |
|---|---|---|---|
| 18 | アクティブファイルの編集 | Editor API を使用します | アクティブファイルの編集に Vault.modify() を使用します |
| 19 | バックグラウンドでのファイル変更 | Vault.process() を使用します | バックグラウンドでの変更に Vault.modify() を使用します |
| 20 | ファイルの削除 | FileManager.trashFile() を使用します | Vault.trash() または Vault.delete() を直接使用します |
| 21 | ファイルの検索 | Vault.getAbstractFileByPath() を使用します | Vault.getFiles().find() ですべてのファイルを反復処理します |
| 22 | ユーザーパス | normalizePath() を使用します | .obsidian のパスをハードコードします。または、ユーザーパスをそのまま使用します |
| 23 | OS の検出 | Platform API を使用します | navigator.platform/userAgent を使用します |
| 24 | ネットワークリクエスト | requestUrl() を使用します | fetch() を使用します |
| 24a | レスポンスの型付け | API 境界で response.text を解析し、検証/型変換を一度行います | any 型の response.json をプラグインコードに渡します |
| 25 | ロギング | コンソールへのログ出力は最小限にし、本番環境の onload/onunload では出力しません | onload/onunload で console.log を使用します |
| 26 | 入力候補 | 組み込みの AbstractInputSuggest を使用します | Liam の TextInputSuggest 実装をコピーします |
| 27 | API の互換性 | minAppVersion を確認して API が利用可能か判断します(例:getSettingDefinitions() には 1.13.0 が必要です) | 宣言した minAppVersion では利用できない API を使用します |
| 28 | 言語の検出 | Obsidian の getLanguage() を使用します | localStorage.getItem('language') または i18next-browser-languagedetector を使用します |
ポップアウトウィンドウの互換性
| # | ルール | ✅ 推奨 | ❌ 非推奨 |
|---|---|---|---|
| 29 | ドキュメント/ウィンドウ | activeDocument と activeWindow を使用する | グローバルな document と window を使用する |
| 29a | ゲッターの取得 | 同じドキュメントを後で使用する場合は、activeDocument を変数に取得しておく | セットアップ時とクリーンアップ時に activeDocument を呼び出す — フォーカスに追従するため、異なるドキュメントが返されることがあります |
| 30 | タイマー | window.setTimeout()、window.setInterval()、window.requestAnimationFrame() などを使用する(ルール 29 の例外) | setTimeout()/setInterval() をそのまま使用する、または activeWindow.setTimeout() を使用する — どちらも prefer-window-timers で警告されます |
| 31 | メインワークスペース UI | 設定画面から this.app.workspace.containerEl.ownerDocument を使用する | 設定ウィンドウからメインワークスペースを更新するために activeDocument を使用する |
注(v0.4.0):
prefer-active-docは引き続きデフォルトで無効です —offとして提供されている唯一の Obsidian ルールです。ポップアウトウィンドウをサポートするには、手動で有効にしてください。
注(v1.13.0): 設定が新しいウィンドウで開くようになりました。設定コールバック内の
activeDocumentはメインの保管庫ではなく、設定ウィンドウを指します。メインワークスペース UI を対象にするには、this.app.workspace.containerEl.ownerDocumentを使用してください。
注:
activeDocument/activeWindowは、フォーカスされたウィンドウを追跡する動的なゲッターです。セットアップ時にactiveDocument.addEventListener()で追加したリスナーは、クリーンアップ時にactiveDocument.removeEventListener()で確実に削除できるとは限りません。登録時に対象を取得するregisterDomEvent()(ルール 6a)を使用してください。
イベント処理
| # | ルール | ✅ 推奨 | ❌ 非推奨 |
|---|---|---|---|
| 31 | エディターでのドロップ/貼り付け | evt.defaultPrevented を確認し、evt.preventDefault() を呼び出す | defaultPrevented を確認せずにエディターでのドロップ/貼り付けを処理する |
スタイリング
| # | ルール | ✅ 推奨 | ❌ 非推奨 |
|---|---|---|---|
| 32 | CSS 変数 | すべてのスタイリングに Obsidian の CSS 変数を使用する | 色、サイズ、間隔をハードコードする |
| 33 | CSS のスコープ | CSS の対象をプラグインのコンテナーに限定する | 範囲の広い CSS セレクターを使用する |
| 34 | 要素のスタイル設定 | styles.css ファイルを使用する(no-forbidden-elements) | <link> 要素または <style> 要素を作成する、JavaScript でスタイルを設定する |
| 34a | !important | セレクターの詳細度を上げるか、CSS 変数を使用する | !important を使用する — ユーザーのテーマやスニペットを上書きします |
| 34b | :has セレクター | 条件の変化に応じて TypeScript からクラスを切り替える | :has を使用する — 範囲の広いセレクターの無効化を引き起こし、パフォーマンス上の問題が発生します |
セキュリティと互換性
| # | ルール | ✅ 推奨 | ❌ 非推奨 |
|---|---|---|---|
| 35 | DOM の作成 | prefer-create-el を介して Obsidian の DOM ヘルパー(createEl()、createDiv()、createSpan()、createSvg()、createFragment())を使用する。リンターは activeDocument.createElement() を activeWindow.createEl() に自動修正します(v0.4.1) | document.createElement()、document.createDocumentFragment() などを使用する |
| 36 | Node.js モジュール | Platform.isDesktop チェックで Node.js のインポートを保護する(no-nodejs-modules) | プラットフォームチェックなしで Node.js モジュールをインポートする |
| 37 | iOS 互換性 | 正規表現の後読みを避ける(iOS < 16.4 との非互換性) | 正規表現の後読みを使用する |
アクセシビリティ(必須)
| # | ルール | ✅ 推奨 | ❌ 非推奨 |
|---|---|---|---|
| 38 | キーボード操作 | すべてのインタラクティブ要素をキーボードで操作できるようにする。すべての要素を Tab で移動できるようにする | キーボードで操作できないインタラクティブ要素を作成する |
| 39 | ARIA ラベル | アイコンボタンに ARIA ラベルを指定する。ツールチップには data-tooltip-position を使用する | ARIA ラベルのないアイコンボタンを使用する |
| 40 | フォーカスインジケーター | Obsidian の CSS 変数と :focus-visible を使用する。タッチ対象を 44×44px 以上にする | フォーカスインジケーターを削除する。タッチ対象を 44×44px 未満にする |
コード品質
| ルール | ✅ 推奨 | ❌ 非推奨 |
|---|---|---|
| サンプルコード | サンプル/テンプレートコードをすべて削除する | MyPlugin、SampleModal のようなクラス名を残す |
| Object.assign | Object.assign({}, defaults, overrides) (object-assign) | Object.assign(defaultsVar, other) — defaults を変更します |
| LICENSE | 著作権者に「Dynalist Inc.」を指定せず、年は現在の年にする (validate-license) | 著作権者に「Dynalist Inc.」を指定したままにする、または古い年を使用する |
| 非同期処理 | async/await を使用する | Promise チェーンを使用する |
| 非推奨パッケージ | 指摘された npm パッケージを Node.js の組み込み機能に置き換える(例:builtin-modules → import { builtinModules } from "node:module") | スキャナーが置き換え可能と指摘するパッケージを使用する |
詳細ガイドライン
特定のトピックの詳細については、以下のリファレンスファイルをご覧ください。
メモリ管理とライフサイクル
registerEvent()、addCommand()、registerDomEvent()、registerInterval()の使用registerDomEvent()と手動のaddEventListenerの比較(およびactiveDocumentのずれに関するバグ)- プラグイン内でビューへの参照を保持しない
- プラグインをコンポーネントとして使用しない
- リーフを適切にクリーンアップする
型安全性
- 型キャストではなく
instanceofを使用する any型を避けるvarよりもconstとletを使用する
UI/UX 標準
- センテンスケースの徹底(TypeScript、JSON ロケール、TS/JS ロケールモジュール)
- ロケールファイルのチェックに
recommendedWithLocalesEn設定を使用する - コマンドの命名規則(「command」、プラグイン名、プラグイン ID を含めない)
- 設定に関するベストプラクティス
- 宣言的な設定 API(1.13 以降):移行パス、コントロールの種類、注意点
ファイルと Vault の操作
- ビューへのアクセスパターン
- Editor API と Vault API
- アトミックなファイル操作
- ファイル管理
- パスの処理
CSS スタイリングのベストプラクティス
- インラインスタイルを避ける
- Obsidian の CSS 変数を使用する
!importantを避ける(詳細度または CSS 変数を使用する):hasセレクターを避ける(代わりに TypeScript からクラスを切り替える)- プラグインのスタイルをスコープ化する
- テーマへの対応
- 間隔とレイアウト
アクセシビリティ(A11y)
- キーボード操作(必須)
- ARIA ラベルとロール(必須)
- ツールチップとアクセシビリティ
- フォーカス管理(必須)
- フォーカス表示スタイル(必須)
- スクリーンリーダーへの対応(必須)
- モバイルとタッチ操作のアクセシビリティ(必須)
- アクセシビリティのチェックリスト
コード品質とベストプラクティス
- サンプルコードの削除
- セキュリティのベストプラクティス
- プラットフォーム互換性
- API 使用のベストプラクティス
- async/await のパターン
- DOM ヘルパー
- 非推奨/置き換え可能なパッケージ(例:
builtin-modules→node:module)
プラグインの提出要件
- リポジトリの構成
- 提出プロセス
- セマンティックバージョニング
- テストのチェックリスト
- 追加リソースと重要な注意事項
Community Plugin Scanner
- スキャナーが実行する内容(ESLint ルールセットと ESLint 以外のチェック)
- スコアカードシステム(健全性、レビュー、開示事項、改善のヒント)
- バージョンスタンプ付き — スキャナーの進化に合わせて更新する単一のファイル
ESLint 設定ガイド
- Community Plugin Scanner への準拠に必要な ESLint 設定一式
typescript-eslintの recommendedTypeChecked が必須である理由- よくある違反と修正方法(未処理の Promise、require インポートなど)
- ポップアウトウィンドウの互換性ルール
プラグイン提出の検証ワークフロー
プラグインを提出する前に、次の手順に従ってください。
- ESLint を実行する —
eslint-plugin-obsidianmdを使用してnpx eslint .を実行し、すべてのエラーと警告を修正します(警告はスコアカードに影響します)。 - マニフェストを検証する —
id、name、description、version、minAppVersionが命名規則と書式ルール(ルール 1~5)に準拠していることを確認します。 - LICENSE を確認する — 著作権者が「Dynalist Inc.」ではなく、年が現在の年であることを確認します。
- モバイルでテストする — 正規表現の後読み、
fetch()、および 44×44px 未満のタッチターゲットがないことを確認します(プラグインがデスクトップ専用と宣言されている場合のみ省略できます)。 - キーボードアクセシビリティを監査する — すべてのインタラクティブ要素を Tab キーで移動し、フォーカスインジケーターと ARIA ラベルがあることを確認します。
- GitHub Release を作成する — タグが
manifest.jsonのバージョンと一致する必要があります。main.js、manifest.json、およびstyles.css(任意)を添付します。 - community.obsidian.md から提出する — サインインし、GitHub アカウントをリンクして、「Plugins → New plugin」に移動し、リポジトリの URL を入力します。開発者ポリシーを確認してから提出してください。
修正後に ESLint が新たなエラーを報告した場合は、ステップ 1 から再実行してください。
スコアカードシステム
公開されたプラグインには、community.obsidian.md に表示されるスコアカードが付与されます。スコアカードはユーザーからの信頼と見つけやすさに影響します。スコアが低いと、ユーザーがインストールをためらう原因になります。
重要なポイント:
- 総合スコア 90%以上を目指してください。
- エラーだけでなく、すべての ESLint 警告を修正してください。警告は公開されます。
- 型を考慮したチェックには
typescript-eslint/recommendedTypeCheckedを使用してください。 - リリースに GitHub アーティファクト証明を追加してください。
スキャナーのチェック、ヘルスメトリクス、レビューのチェック、よくある警告、改善のヒントの詳細については、Community Plugin Scanner を参照してください。
コードのレビュー/作成時
コードレビューと実装には、次のチェックリストを使用してください。
- メモリ管理:コンポーネントとビューは適切に管理されていますか?
- 型安全性:キャストではなく
instanceofを使用していますか? - UI テキスト:すべてセンテンスケースになっていますか?
- コマンドの命名:冗長な語が含まれていませんか?
- ファイル操作:推奨される API を使用していますか?
- モバイル互換性:iOS と互換性のない機能はありませんか?
- サンプルコード:ボイラープレートはすべて削除されていますか?
- マニフェスト:バージョンは正しく、構造は有効ですか?
- アクセシビリティ:キーボード操作、ARIA ラベル、フォーカスインジケーターに対応していますか?
- テスト:マウスを使わずにプラグインを操作できますか?
- タッチターゲット:すべてのインタラクティブ要素が 44×44px 以上ですか?
- フォーカススタイル:
:focus-visibleと適切な CSS 変数を使用していますか? - 設定:1.13 以降では宣言型の
getSettingDefinitions()を使用していますか?保存されるデータはすべてplugin.settingsにまとめられていますか?
よくあるパターン
適切なコマンド登録
// ✅ 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();
}
});
宣言型の設定(1.13 以降)
// ✅ CORRECT — getSettingDefinitions() replaces display() on 1.13+
getSettingDefinitions() {
return [
{
name: 'Mode',
control: {
type: 'dropdown',
key: 'mode',
defaultValue: 'fast',
options: { fast: 'Fast', thorough: 'Thorough' },
},
},
];
}
テーマ対応 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;
}
Obsidian プラグインの開発を支援する際は、これらのルールを積極的に適用し、ガイドラインに基づいて改善点を提案してください。各トピックの詳しい情報については、詳細なリファレンスファイルを参照してください。