Obsidian-Plugin entwickeln mit TypeScript
Von der offiziellen Vorlage über Befehle, Einstellungen und Ereignisse bis zur Einreichung im Verzeichnis der Community-Plugins: so entsteht ein Obsidian-Plugin.
Ein Obsidian-Plugin entwickeln Sie in TypeScript auf Basis der offiziellen Beispielvorlage von Obsidian. Das Projekt wird zu einer einzigen main.js gebündelt, die zusammen mit manifest.json im Plugin-Ordner eines Tresors liegt. Ihre Plugin-Klasse meldet in onload() Befehle, Einstellungen und Ereignis-Listener an, und Obsidian räumt alles, was Sie über die Registrierungsmethoden angelegt haben, beim Deaktivieren wieder auf.
Obsidian ist eine Electron-Anwendung. Plugins laufen deshalb mit weitreichenden Rechten im selben Prozess wie die App, ohne die Sandbox, die Sie von Browser-Erweiterungen kennen. Das macht den Einstieg leicht und verlangt zugleich Sorgfalt.
Was Sie vorab brauchen
- Node.js und npm für Build und Abhängigkeiten.
- Git und ein GitHub-Konto, denn Vorlage und Veröffentlichung laufen über GitHub.
- Einen eigenen Test-Tresor. Entwickeln Sie nie in Ihrem echten Notiztresor. Ein fehlerhaftes Plugin kann Dateien verändern oder löschen.
- Grundkenntnisse in TypeScript. Wie Sie ein Build-Setup selbst aufsetzen, erklärt der Beitrag TypeScript und Vite für Erweiterungen einrichten. Für Obsidian bringt die Vorlage bereits alles mit.
Projekt aus der Vorlage anlegen
- Legen Sie einen leeren Tresor an, etwa
plugin-test, und öffnen Sie ihn in Obsidian. - Erzeugen Sie auf GitHub aus der Beispielvorlage von Obsidian (Repository
obsidian-sample-plugin) ein eigenes Repository und klonen Sie es nachplugin-test/.obsidian/plugins/. - Installieren Sie die Abhängigkeiten mit
npm installund starten Sie den Build im Beobachtungsmodus mitnpm run dev. - Aktivieren Sie in Obsidian unter Einstellungen die Community-Plugins und schalten Sie Ihr Plugin ein.
Der Ordnername sollte zur id im Manifest passen. Der Build erzeugt main.js direkt im Projektordner, wo Obsidian sie erwartet.
Die Dateien im Überblick
| Datei | Aufgabe |
|---|---|
manifest.json |
Name, ID, Version, Mindestversion von Obsidian, Beschreibung, Autor |
main.ts |
Quellcode der Plugin-Klasse |
main.js |
gebündeltes Ergebnis, das Obsidian lädt |
styles.css |
optionale Stile, werden automatisch geladen |
versions.json |
ordnet Plugin-Versionen der jeweils nötigen Obsidian-Version zu |
esbuild.config.mjs |
Build-Konfiguration der Vorlage |
Ein Manifest sieht etwa so aus:
{
"id": "tagesnotiz-zaehler",
"name": "Tagesnotiz-Zähler",
"version": "1.0.0",
"minAppVersion": "1.5.0",
"description": "Zählt Wörter in der aktuellen Notiz und zeigt sie in der Statusleiste.",
"author": "Redaktion AddOn-Coders",
"isDesktopOnly": false
}
Die id ist dauerhaft. Sie bestimmt den Ordnernamen bei allen Nutzern und darf nach der Veröffentlichung nicht mehr geändert werden. Die minAppVersion setzen Sie auf die älteste Obsidian-Version, mit der Sie tatsächlich getestet haben.
Die Plugin-Klasse
Jedes Plugin exportiert eine Klasse, die von Plugin erbt. Das folgende Beispiel registriert einen Befehl, ein Symbol in der Seitenleiste und eine Anzeige in der Statusleiste:
import { Notice, Plugin, MarkdownView } from "obsidian";
export default class WortZaehler extends Plugin {
async onload() {
const status = this.addStatusBarItem();
this.addCommand({
id: "woerter-zaehlen",
name: "Wörter der aktuellen Notiz zählen",
editorCallback: (editor) => {
const anzahl = editor.getValue().split(/\s+/).filter(Boolean).length;
new Notice(`${anzahl} Wörter`);
},
});
this.addRibbonIcon("hash", "Wörter zählen", () => {
const view = this.app.workspace.getActiveViewOfType(MarkdownView);
if (!view) return;
const anzahl = view.editor.getValue().split(/\s+/).filter(Boolean).length;
status.setText(`${anzahl} Wörter`);
});
}
onunload() {
// Über add... und register... angelegte Elemente entfernt Obsidian selbst.
}
}
Wichtig ist der Unterschied zwischen callback und editorCallback bei Befehlen: editorCallback erscheint nur, wenn ein Editor aktiv ist, und bekommt ihn direkt übergeben. So vermeiden Sie Fehler, wenn gerade keine Notiz geöffnet ist.
Auf Ereignisse reagieren
Viele Plugins reagieren auf Änderungen im Tresor oder im Arbeitsbereich. Registrieren Sie solche Listener immer über this.registerEvent(), damit Obsidian sie beim Deaktivieren entfernt:
this.registerEvent(
this.app.vault.on("modify", (datei) => {
console.log("geändert:", datei.path);
})
);
Dasselbe Prinzip gilt für DOM-Ereignisse (registerDomEvent) und Intervalle (registerInterval). Wer stattdessen direkt addEventListener oder setInterval nutzt, hinterlässt nach dem Deaktivieren laufenden Code. Das ist einer der häufigsten Gründe für Nachfragen bei der Prüfung.
Dateien lesen und schreiben
Für Notizen nutzen Sie die Tresor-API statt direkter Dateisystemzugriffe. app.vault.read() liest eine Datei, app.vault.modify() schreibt sie. Für Änderungen an bestehenden Dateien empfiehlt Obsidian app.vault.process(), das Lesen und Schreiben in einem Schritt erledigt und so Konflikte mit anderen Plugins vermeidet. Frontmatter bearbeiten Sie am sichersten über app.fileManager.processFrontMatter(). Direkte Zugriffe über Node.js funktionieren nur am Desktop und umgehen die Mechanismen, mit denen Obsidian Änderungen verfolgt.
Einstellungen speichern
Einstellungen legt ein Plugin mit loadData() und saveData() in einer data.json in seinem eigenen Ordner ab. Eine eigene Einstellungsseite bauen Sie mit einer Klasse, die von PluginSettingTab erbt:
interface Einstellungen { zielWoerter: number }
const STANDARD: Einstellungen = { zielWoerter: 500 };
// in onload():
this.einstellungen = Object.assign({}, STANDARD, await this.loadData());
this.addSettingTab(new MeineEinstellungen(this.app, this));
In der Einstellungsklasse erzeugen Sie Felder mit new Setting(containerEl), etwa ein Textfeld mit addText() oder einen Schalter mit addToggle(). Speichern Sie bei jeder Änderung mit saveData(). Das Object.assign mit Standardwerten sorgt dafür, dass neue Einstellungen nach einem Update nicht als undefined ankommen.
Markdown erweitern und eigene Ansichten
Viele beliebte Plugins verändern, wie Notizen dargestellt werden. Dafür gibt es zwei Wege. Mit registerMarkdownCodeBlockProcessor() übernehmen Sie die Darstellung eines Codeblocks mit eigener Sprachkennung, etwa zaehler direkt hinter den öffnenden Backticks. Obsidian übergibt Ihnen den Inhalt des Blocks und ein HTML-Element, in das Sie eine Tabelle, ein Diagramm oder eine Auswertung zeichnen. Mit registerMarkdownPostProcessor() bearbeiten Sie die fertige Leseansicht allgemein, etwa um bestimmte Muster hervorzuheben.
Für eigene Bereiche in der Seitenleiste, etwa eine Liste offener Aufgaben, registrieren Sie mit registerView() eine Ansicht, die von ItemView erbt. Die Ansicht öffnen Sie dann über einen Befehl in einem Blatt (Leaf) des Arbeitsbereichs. Beachten Sie, dass die Leseansicht und der Bearbeitungsmodus unterschiedlich funktionieren: Was im Lesemodus über Post-Prozessoren läuft, braucht im Live-Vorschau-Modus des Editors häufig eine Editor-Erweiterung auf Basis von CodeMirror 6. Planen Sie diesen Mehraufwand ein, wenn Ihr Plugin in beiden Modi gleich aussehen soll.
Testen und Fehler finden
Obsidian bringt die Entwicklerwerkzeuge von Chromium mit. Am Desktop öffnen Sie sie über die Tastenkombination Strg+Umschalt+I (macOS: Cmd+Option+I). Dort sehen Sie Konsolenausgaben, Fehler und können Haltepunkte setzen. Hilfreich ist eine kurze Testliste vor jeder Version:
- Plugin aktivieren, deaktivieren und erneut aktivieren: Bleiben Statusleiste, Symbole oder Listener doppelt zurück?
- Obsidian ohne geöffnete Notiz starten: Wirft ein Befehl einen Fehler?
- Mit einem großen Tresor testen: Bremst der Code beim Start?
- In der mobilen App öffnen, sofern das Plugin nicht als Desktop-only markiert ist.
Starten Sie aufwendige Arbeit nicht direkt in onload(), sondern erst, wenn der Arbeitsbereich bereit ist, etwa über this.app.workspace.onLayoutReady(). So verlängert Ihr Plugin den Start der App nicht.
Veröffentlichen im Verzeichnis der Community-Plugins
- Version setzen: Erhöhen Sie
versioninmanifest.jsonund tragen Sie die Version inversions.jsonein. Die Vorlage bringt dafür ein npm-Skript mit. - GitHub-Release anlegen: Der Tag muss genau der Versionsnummer entsprechen, ohne vorangestelltes „v“. Hängen Sie
main.js,manifest.jsonund gegebenenfallsstyles.cssals einzelne Dateien an. - README und Lizenz ergänzen: Beschreiben Sie Funktion, Bedienung und offen gelegte Netzwerkzugriffe. Eine Lizenzdatei im Repository gehört dazu.
- Einreichen: Stellen Sie einen Pull Request an das Repository
obsidian-releases, in dem Ihr Plugin in die Liste der Community-Plugins aufgenommen wird. Ein automatischer Check prüft Manifest und Release, danach sieht sich das Obsidian-Team den Code an.
Spätere Updates brauchen keinen neuen Pull Request. Obsidian liest die Versionen aus Ihren GitHub-Releases. Ein neues Release mit höherer Versionsnummer genügt.
Häufige Gründe für Rückfragen
| Problem | Besser |
|---|---|
innerHTML mit Nutzereingaben |
DOM-Hilfsmethoden wie createEl() oder setText() |
Listener ohne registerEvent |
Registrierungsmethoden des Plugins nutzen |
| feste Stile mit Farbwerten | CSS-Variablen von Obsidian, damit Themes funktionieren |
| Beispielcode der Vorlage nicht entfernt | ungenutzte Befehle und Klassen löschen |
| Wort „Obsidian“ im Plugin-Namen | beschreibenden Namen ohne Produktnamen wählen |
| Node-APIs ohne Desktop-Kennzeichnung | isDesktopOnly setzen oder Plattform prüfen |
Die genauen Richtlinien veröffentlicht Obsidian in der Entwicklerdokumentation. Sie werden gelegentlich angepasst, deshalb lohnt vor jeder Einreichung ein aktueller Blick hinein. Allgemeine Hinweise zu Versionsnummern, Changelog und Pflege finden Sie im Bereich Add-ons veröffentlichen und pflegen.
Obsidian im Vergleich mit anderen Plugin-Plattformen
Wer mehrere Produktivitäts-Apps erweitert, erkennt Muster wieder. Auch Figma nutzt eine manifest.json und TypeScript, trennt aber Dokumentzugriff und Oberfläche in zwei Teile mit Nachrichtenaustausch. Diesen Aufbau beschreibt der Artikel Figma-Plugin erstellen. Obsidian ist im Vergleich offener: Ihr Code hat direkten Zugriff auf Oberfläche und Dateien. Diese Freiheit ist bequem, verlangt aber umso mehr Zurückhaltung bei Datenzugriffen, Netzwerkverbindungen und Änderungen an fremden Notizen.
Fragen und Antworten
- Brauche ich TypeScript, oder geht auch reines JavaScript?
- Obsidian lädt am Ende nur eine main.js, reines JavaScript funktioniert also. Die offizielle Vorlage ist aber auf TypeScript ausgelegt, und die Typdefinitionen des Pakets obsidian zeigen Ihnen im Editor sofort, welche Methoden und Parameter es gibt. Für mehr als ein paar Zeilen lohnt sich TypeScript.
- Läuft mein Plugin auch in der mobilen App?
- Grundsätzlich ja, solange Sie keine Node.js- oder Electron-Funktionen verwenden, die es nur am Desktop gibt. Wer solche Funktionen braucht, setzt in der manifest.json isDesktopOnly auf true. Testen Sie mobile Versionen gesondert, denn Oberfläche und Bildschirmgröße unterscheiden sich deutlich.
- Warum wird mein Plugin nach einer Änderung nicht neu geladen?
- Obsidian lädt Plugin-Code nur beim Aktivieren. Nach einem Build schalten Sie das Plugin in den Einstellungen aus und wieder ein oder laden Obsidian neu. Viele Entwickler nutzen dafür ein Hilfs-Plugin, das Plugins bei Dateiänderungen automatisch neu lädt.
- Darf ich Daten aus dem Tresor an einen Server senden?
- Technisch ist das möglich, Obsidian erwartet aber Transparenz. Netzwerkzugriffe, Telemetrie und Konten müssen in der README offengelegt werden. Notizen sind oft sehr persönlich, deshalb sollte ein Plugin standardmäßig lokal arbeiten und Übertragungen nur nach ausdrücklicher Aktivierung durchführen.
Weiterlesen
- TypeScript und Vite einrichten
Ein Build-Setup für Erweiterungen: TypeScript-Konfiguration, Typen für die Browser-APIs, mehrere Einstiegspunkte mit Vite, die Sonderrolle der Content Scripts und Alternativen wie WXT.
- Figma-Plugin erstellen
Wie Hauptcode und Oberfläche eines Figma-Plugins zusammenarbeiten, wie Sie Ebenen lesen und erzeugen und was bis zur Veröffentlichung in der Figma Community zu beachten ist.
- Veröffentlichen
Stores und Prüfprozesse, Versionsnummern und Changelog, Lizenzen und Bezahlmodelle: was nach dem letzten Commit kommt und warum ein Add-on nie ganz fertig ist.