Plugins

Plugins entwickeln

Du möchtest FediSuite um eigene Funktionen erweitern? Diese Seite erklärt, wie ein Plugin aufgebaut ist, wie Du mit dem Scaffold-Generator startest und welche Möglichkeiten die Plugin-API (Version 1) bietet. Für den Einstieg brauchst Du kein tiefes Vorwissen.

Für wen ist diese Seite?

Diese Seite richtet sich an alle, die eigene Plugins für FediSuite bauen oder verstehen möchten, wie das Plugin-System von innen funktioniert. Der Scaffold-Generator übernimmt die Grundarbeit, und die Beispiele auf dieser Seite zeigen, was wohin gehört. Die eigentliche Logik Deines Plugins schreibst Du selbst.

Diese Seite ist für Dich, wenn …

  • … Du ein eigenes Plugin entwickeln möchtest.
  • … Du verstehen möchtest, wie Plugins aufgebaut sind.
  • … Du ein bestehendes Plugin anpassen möchtest.

Diese Seite ist nicht für Dich, wenn …

Voraussetzungen

Für ein einfaches Plugin reichen grundlegende JavaScript-Kenntnisse. Der Server-Teil eines Plugins ist ein ES-Modul, das eine Funktion register(context) exportiert.

Grundkenntnisse JavaScript Empfohlen

Plugins werden in JavaScript geschrieben. Für einfache Admin-Seiten oder Widgets genügt es, Funktionen und Objekte zu kennen.

Laufende FediSuite-Testinstanz mit eingebundenem plugins/-Ordner Für Tests nötig

Zum Ausprobieren brauchst Du eine FediSuite-Instanz, bei der ./plugins nach /app/plugins gemountet ist (Standard im Self-Hosting-Repository). Teste neue Plugins besser nicht auf einer Instanz mit echten Konten, denn ein Plugin läuft mit denselben Rechten wie die Anwendung selbst.

Docker Compose oder Node.js Für das Scaffold

Der Scaffold-Generator steckt im App-Container, dort ist Node.js bereits vorhanden. Alternativ führst Du ihn aus dem Quellcode-Repository mit einer lokalen Node.js-Installation aus. Prüfen mit: node --version

Git Pflicht

Zum Klonen des Plugin-Repositories und für Versionierung und Updates.

Vertrauensmodell: Plugins laufen im Hauptprozess von FediSuite, ohne Sandbox. Der Server-Code eines Plugins hat Zugriff auf die Datenbank-Verbindung der Anwendung, und Plugin-Webseiten werden direkt in die App eingebettet. FediSuite begrenzt nur, was ein Plugin über die Kontext-API registrieren darf (siehe Berechtigungen). Das ist keine Isolation gegen bösartigen Code, installiere also nur Plugins, denen Du vertraust.

Schnelleinstieg mit dem Scaffold

Der empfohlene Weg zum eigenen Plugin ist der Scaffold-Generator. Er ist Teil der FediSuite-Anwendung (scripts/create-plugin-scaffold.mjs, aufgerufen über npm run plugins:create) und legt mit einem Befehl eine lauffähige Plugin-Struktur an: plugin.json, server/index.js, Sprachdateien, bei Bedarf Web-Seiten und eine README. Du fängst also nicht bei null an.

Variante 1: im laufenden App-Container

Öffne eine Shell im App-Container und führe den Befehl im Verzeichnis /app aus. Dort öffnet docker compose exec die Shell standardmäßig. Ohne --target entsteht das Plugin unter plugins/<id>, im Container also in /app/plugins und damit im gemounteten Ordner ./plugins auf dem Host:

bash
# Shell im App-Container öffnen
docker compose exec app sh

# Scaffold im Hauptverzeichnis der Anwendung ausführen
npm run plugins:create -- \
  --id mein-plugin \
  --name "Mein Plugin" \
  --author "Dein Name" \
  --presets admin-page
Ausgabe
Plugin scaffold created successfully.
Path: /app/plugins/mein-plugin
Plugin ID: mein-plugin
Presets: admin-page
Dateibesitzer: Der Container läuft ohne eigenen Benutzer, die erzeugten Dateien gehören auf dem Host deshalb meist root. Übernimm sie bei Bedarf mit sudo chown -R "$USER": plugins/mein-plugin. Im Entwicklungs-Compose des Quellcode-Repositories (docker-compose.build.yml) ist ./plugins nur lesbar eingebunden (:ro). Lege das Plugin dort mit Variante 2 an.

Variante 2: aus dem Quellcode-Repository

Mit Node.js auf dem Rechner klonst Du das Hauptrepository FediSuite-Docker-Image (öffnet in neuem Tab) und gibst mit --target den Zielordner an. Das Ziel darf noch nicht existieren:

bash
git clone https://forge.chrislo.de/FediSuite/FediSuite-Docker-Image.git
cd FediSuite-Docker-Image
npm run plugins:create -- \
  --id mein-plugin \
  --name "Mein Plugin" \
  --author "Dein Name" \
  --presets admin-page \
  --target /pfad/zu/fedisuite/plugins/mein-plugin

Die Optionen

--id

Pflicht. Die technische ID des Plugins. Der Generator macht daraus Kleinbuchstaben, Ziffern und Bindestriche (aus „Mein Plugin“ wird mein-plugin). Die ID dient als eindeutiger Schlüssel in FediSuite, im API-Pfad /api/plugins/<id>/ und im Sprach-Namespace. Einmal vergeben, nicht mehr ändern.

--name

Pflicht. Der Anzeigename, der in der Plugin-Verwaltung erscheint. Er darf Leerzeichen und Sonderzeichen enthalten.

--author

Pflicht. Dein Name oder der Deiner Organisation. Er erscheint in der Plugin-Verwaltung.

--presets

Optional. Welche Bausteine angelegt werden. Mehrere Presets trennst Du mit Kommas. Ohne Angabe entsteht ein Plugin ohne Bausteine.

--description

Optional. Die Beschreibung für die plugin.json. Standard: „A FediSuite plugin scaffold for <Name>.“

--version

Optional. Die Startversion. Standard: 1.0.0.

--target

Optional. Der Zielordner. Standard: plugins/<id> im aktuellen Verzeichnis. Existiert er schon, bricht der Generator ab.

--help

Zeigt die Hilfe mit allen Optionen und Presets.

Verfügbare Presets

Jedes Preset legt den passenden Baustein samt Beispieltexten an und trägt Capability und Berechtigungen in die plugin.json ein. Presets lassen sich kombinieren: --presets app-page,dashboard-widget

admin-page Capability: admin.section Berechtigungen: admin.sections, web.runtime, api.routes

Seite im Admin-Bereich mit Markdown-Inhalt und einer Beispiel-Webseite, dazu eine Status-Route.

app-page Capability: app.section Berechtigungen: app.sections, web.runtime, api.routes

Eigener Eintrag in der Seitenleiste für alle Nutzer*innen, ebenfalls mit Markdown-Inhalt, Beispiel-Webseite und Status-Route.

dashboard-widget Capability: dashboard.widget Berechtigungen: dashboard.widgets

Statistik-Widget auf einem Tab der Seite „Analysen“.

composer-extension Capability: composer.extension Berechtigungen: composer.extensions

Zusätzliche Felder im Composer und eine Textumwandlung vor dem Veröffentlichen.

provider Capability: provider Berechtigungen: providers

Eine zusätzliche Plattform, deren Konten sich verbinden lassen (Beispiel-Verbindungsfluss zum Anpassen).

auth-provider Capability: auth.provider Berechtigungen: auth.providers

Ein zusätzlicher Login für FediSuite selbst (Beispiel für Start und Rückkehr).

insights-provider Capability: insights.provider Berechtigungen: insight.providers

Ein Anbieter für zusätzliche Tipps zu einem Konto, zunächst mit einem festen Beispiel-Tipp.

settings Capability: settings.section Berechtigungen: settings.global.read/write, settings.user.read/write

Ein settingsSchema im Manifest für plugin-eigene Einstellungen (Beispielfelder für global und pro Nutzer*in).

Empfehlung für den Einstieg: Starte mit --presets admin-page. Eine einfache Admin-Seite zeigt das komplette Grundgerüst, ohne dass Du Provider-Logik brauchst. Weitere Presets kannst Du später ergänzen. Das Repository FediSuite-Plugins-Repository (öffnet in neuem Tab) enthält mit my-plugin ein solches Scaffold-Ergebnis zum Anschauen.

Die Plugin-Struktur

Jedes Plugin ist ein Ordner direkt im plugins/-Verzeichnis. FediSuite erkennt ein Plugin daran, dass in diesem Ordner unmittelbar eine plugin.json liegt, tiefer verschachtelte Ordner werden nicht durchsucht. Der Ordnername entspricht üblicherweise der Plugin-ID, maßgeblich ist aber die id in der plugin.json.

Mindestens nötig

mein-plugin/
├── plugin.json       ← Pflicht
└── server/
    └── index.js      ← Pflicht

Vom Scaffold erzeugt (admin-page)

mein-plugin/
├── plugin.json
├── README.md
├── server/
│   └── index.js
├── i18n/
│   ├── de.json
│   ├── en.json
│   ├── es.json
│   ├── fr.json
│   └── it.json
└── web/             ← nur bei admin-page / app-page
    ├── manifest.json
    ├── admin-main.html
    └── main.js
plugin.json

Das Manifest: beschreibt das Plugin, seine Berechtigungen und Einstiegspunkte. FediSuite liest diese Datei als erstes.

server/index.js

Die Einstiegsdatei auf der Serverseite. Hier registrierst Du alles, was das Plugin in FediSuite einbindet.

i18n/<sprache>.json

Sprachdateien (optional, aber empfohlen). Der Dateiname ist der Sprachcode, das Scaffold legt de, en, it, fr und es an. Die Dateien enthalten die Texte des Plugins als verschachtelte Schlüssel mit Zeichenketten.

web/manifest.json

Optional. Wird nur benötigt, wenn das Plugin eigene HTML/JS-Seiten in FediSuite einbettet.

README.md

Vom Scaffold erzeugte Notizen zum Plugin. FediSuite liest die Datei nicht.

Das Plugin-Manifest (plugin.json)

Die plugin.json ist die wichtigste Datei eines Plugins. Sie liegt im Stammverzeichnis des Plugin-Ordners und sagt FediSuite, wer das Plugin ist, was es mitbringt und welche Berechtigungen es braucht. Fehlt ein Pflichtfeld oder ist die Datei ungültig, wird das Plugin nicht geladen und mit dem Status „Fehler“ angezeigt.

So sieht das Manifest aus, das das Scaffold mit --presets admin-page erzeugt:

plugin.json
{
  "id": "mein-plugin",
  "name": "Mein Plugin",
  "version": "1.0.0",
  "pluginApiVersion": 1,
  "description": "A FediSuite plugin scaffold for Mein Plugin.",
  "i18nDir": "./i18n",
  "displayNameKey": "meta.name",
  "descriptionKey": "meta.description",
  "author": "Dein Name",
  "license": "GPL-3.0-or-later",
  "capabilities": [
    "admin.section"
  ],
  "requiredPermissions": [
    "admin.sections",
    "web.runtime",
    "api.routes"
  ],
  "webEntry": "./web/manifest.json",
  "serverEntry": "./server/index.js"
}

Pflichtfelder

id string

Die stabile, technische ID. Erlaubt sind Buchstaben, Ziffern, Punkte, Bindestriche und Unterstriche. Sie muss mindestens 2 Zeichen lang sein und mit einem Buchstaben oder einer Ziffer beginnen. Die ID wird im API-Pfad (/api/plugins/<id>/...) und im Sprach-Namespace (plugin.<id>) verwendet. Einmal vergeben, nicht mehr ändern.

name string

Der Anzeigename, wenn keine Übersetzung vorhanden ist. Mit displayNameKey wird er durch den übersetzten Text ersetzt.

version string

Die Versionsnummer des Plugins, üblicherweise nach dem Schema Major.Minor.Patch, zum Beispiel "1.0.0".

pluginApiVersion number

Die Plugin-API-Version, für die das Plugin geschrieben wurde. Sie muss genau der Version entsprechen, die FediSuite erwartet. Aktuell ist das 1 (Umgebungsvariable PLUGIN_API_VERSION). Bei einer anderen Version wird das Plugin abgelehnt.

description string

Eine kurze Beschreibung, die ohne Übersetzung angezeigt wird. Mit descriptionKey wird sie durch den übersetzten Text ersetzt.

author string

Name der Autor*in oder der Organisation.

license string

Die Lizenz des Plugins, zum Beispiel "GPL-3.0-or-later" oder "MIT".

capabilities array

Welche Arten von Erweiterung das Plugin liefert, zum Beispiel ["admin.section"]. Das Feld muss vorhanden sein (ein leeres Array genügt). FediSuite zeigt die Werte in der Plugin-Verwaltung an. Welche Funktionen das Plugin registrieren darf, bestimmt requiredPermissions.

serverEntry string

Relativer Pfad zur JavaScript-Einstiegsdatei auf der Serverseite, üblicherweise "./server/index.js". Die Datei muss existieren und eine Funktion register exportieren.

Berechtigungen, Texte und Web-Seiten

requiredPermissions array

Die Berechtigungen, die das Plugin braucht, zum Beispiel ["admin.sections"]. Formal ist das Feld optional. Sobald das Plugin aber etwas registriert, ohne die passende Berechtigung zu nennen, scheitert der Start mit einer klaren Fehlermeldung. Details im nächsten Abschnitt.

i18nDir string

Pfad zum Verzeichnis mit den Sprachdateien, zum Beispiel "./i18n". Ist das Feld gesetzt, muss das Verzeichnis existieren.

displayNameKey string

Sprachschlüssel für den Anzeigenamen, zum Beispiel "meta.name". Erlaubt sind Buchstaben, Ziffern, Punkte, Bindestriche und Unterstriche.

descriptionKey string

Sprachschlüssel für die Beschreibung, zum Beispiel "meta.description".

webEntry string

Pfad zum Web-Manifest, zum Beispiel "./web/manifest.json". Nur setzen, wenn das Plugin eigene HTML/JS-Seiten einbettet. Dafür ist die Berechtigung web.runtime Pflicht.

Weitere optionale Felder

settingsSchema object

Beschreibt plugin-eigene Einstellungen. Siehe Abschnitt „Plugin-Einstellungen“.

homepage, repository string

Links zur Projektseite und zum Quellcode. Die Plugin-Verwaltung zeigt sie in den Details an.

minAppVersion, maxAppVersion string

Kleinste und größte FediSuite-Version, mit der das Plugin zusammenarbeitet, zum Beispiel "2.0.0". Liegt die laufende Version außerhalb, wird das Plugin abgelehnt.

requiredEnv array

Namen von Umgebungsvariablen, die das Plugin erwartet. Die Plugin-Verwaltung listet sie auf, FediSuite prüft sie nicht.

Pfade bleiben im Plugin-Ordner: serverEntry, webEntry und i18nDir sind relativ zum Plugin-Ordner. FediSuite lehnt Pfade ab, die aus dem Ordner herausführen, auch über Symlinks.

Capabilities und Berechtigungen

In der plugin.json stehen zwei Felder, die leicht zu verwechseln sind: capabilities und requiredPermissions.

capabilities

Beschreiben, welche Art von Erweiterung das Plugin liefert. Die Plugin-Verwaltung zeigt sie an. FediSuite prüft nicht, ob sie zu dem passen, was das Plugin tatsächlich registriert.

requiredPermissions

Bestimmen, was das Plugin registrieren und nutzen darf. Jede Methode des Kontexts prüft die passende Berechtigung. Fehlt sie, bricht der Start des Plugins mit einer Fehlermeldung ab.

Welche Capability gehört zu welcher Berechtigung?

Typ capability permission Beschreibung
Admin-Seite admin.section admin.sections

Eine Seite im Admin-Bereich. Eigene Web-Seiten brauchen web.runtime, eigene API-Routen api.routes.

Optional: web.runtime, api.routes

App-Seite app.section app.sections

Ein Eintrag in der Seitenleiste, sichtbar für alle angemeldeten Nutzer*innen.

Optional: web.runtime, api.routes

Dashboard-Widget dashboard.widget dashboard.widgets

Ein Widget auf einem Tab der Seite „Analysen“.

Composer-Erweiterung composer.extension composer.extensions

Zusätzliche Felder und Transformationen im Composer.

Provider / Connector provider providers

Verbindet eine zusätzliche Plattform mit FediSuite (wie das Bluesky-Plugin).

Optional: web.runtime

Login-Provider auth.provider auth.providers

Ein zusätzlicher Login für FediSuite selbst.

Tipp-Provider insights.provider insight.providers

Liefert zusätzliche Tipps zu einem Konto. Beachte den Singular „insight“ in der Berechtigung.

Plugin-Einstellungen settings.section settings.<bereich>.read/write

Lesen und Schreiben der Einstellungen. Der Bereich ist global, user oder account, und es gibt je eine Berechtigung für Lesen und Schreiben.

Weitere Berechtigungen, die unabhängig vom Plugin-Typ gelten:

api.routes

Eigene API-Routen unter /api/plugins/<id>/ registrieren (auth: user oder public).

api.routes.admin

Zusätzlich nötig für Routen mit auth: admin.

web.runtime

Eigene Web-Seiten einbetten. Pflicht, sobald die plugin.json ein webEntry enthält.

hooks

Hooks registrieren. Zusätzlich braucht jedes Ereignis die Berechtigung seiner Gruppe, siehe Abschnitt „Hooks“.

hooks.server, hooks.posts, hooks.accounts, hooks.insights, hooks.settings

Die Gruppen-Berechtigungen für Hooks.

settings.global.read, settings.global.write

Globale Plugin-Einstellungen lesen und schreiben. Pflicht für die Karte in der Plugin-Verwaltung.

settings.user.read, settings.user.write

Einstellungen pro Nutzer*in lesen und schreiben. Pflicht für den Abschnitt in den Einstellungen.

settings.account.read, settings.account.write

Einstellungen pro verbundenem Konto lesen und schreiben.

Wichtig: Ein Plugin, das einen Provider registriert, muss providers deklarieren, sonst scheitert der Start mit der Meldung Plugin "…" must declare permission "providers" to register provider integrations. Das Scaffold trägt die passenden Berechtigungen für die gewählten Presets automatisch ein.

Die Einstiegsdatei: server/index.js

Die Datei server/index.js ist das Herzstück jedes Plugins. FediSuite importiert sie beim Start des Plugins und ruft die exportierte Funktion register(context) auf. Sie darf async sein. Gibt sie eine Funktion zurück, ruft FediSuite diese beim Deaktivieren auf, damit das Plugin aufräumen kann. Dasselbe leistet context.onDispose(callback), auch mehrfach.

Das minimale Gerüst:

server/index.js
export function register(context) {
  // Hier registrierst Du alles, was das Plugin in FediSuite einbindet.
  // Der context-Parameter ist Deine Schnittstelle zu FediSuite.
}

Was ist context?

context ist die offizielle Schnittstelle des Plugins zu FediSuite. Über ihn registrierst Du die Funktionen Deines Plugins. Jede Registrierung prüft die passende Berechtigung aus der plugin.json. Die Methoden mit dem Präfix registerSimple… und registerMarkdown… sind Komfort-Helfer für den Normalfall, die Methoden ohne diese Präfixe geben Dir volle Kontrolle über die Definition.

Informationen über das Plugin

context.plugin

Enthält die Metadaten des Plugins, zum Beispiel context.plugin.plugin_id für die Plugin-ID.

context.namespace

Der Sprach-Namespace des Plugins, zum Beispiel plugin.mein-plugin.

context.settings

Die beim Start geladenen globalen Einstellungen. Für aktuelle Werte oder andere Bereiche nutze getSettings().

context.hasPermission(name), context.requirePermission(name)

Prüft, ob das Plugin eine Berechtigung deklariert hat. requirePermission wirft bei einer fehlenden Berechtigung einen Fehler.

Seiten & UI

registerMarkdownAdminPage()

Einfachster Weg zu einer Admin-Seite: Der Inhalt kommt als Markdown aus der Sprachdatei.

registerMarkdownAppPage()

Wie oben, aber als Eintrag der Seitenleiste für alle Nutzer*innen (zusätzlich mit navLabelKey für die Beschriftung des Menüeintrags).

registerAdminSection(), registerAppSection()

Die Basis-Methoden hinter den Markdown-Helfern. Eine Definition besteht aus id, title, description, badge und content ({ markdown } oder { text }). App-Seiten haben zusätzlich nav_label. Für eigene HTML/JS-Seiten gehört die id zu einem Eintrag im web/manifest.json.

Widgets & Erweiterungen

registerSimpleDashboardWidget()

Statistik-Widget mit Titel, Werten und optionaler Liste.

registerDashboardWidget()

Dashboard-Widget mit vollständiger Definition (title, description, badge, content mit stats, items, text oder markdown).

registerSimpleComposerExtension()

Felder und Transformation im Composer.

registerComposerExtension()

Composer-Erweiterung mit vollständiger Definition (fields, sections, instanceTypes).

Provider

registerSimpleProvider(), registerProvider()

Verbindet eine zusätzliche Plattform. registerSimpleProvider ist der Einstieg, registerProvider gibt die volle Kontrolle (siehe Bluesky-Plugin).

registerSimpleAuthProvider(), registerAuthProvider()

Fügt einen Login-Provider hinzu.

registerOidcAuthProvider()

Fertiger Login über OpenID Connect. Er braucht die Berechtigung auth.providers, und Aussteller und Client-Angaben können aus den Plugin-Einstellungen kommen.

registerSimpleInsightProvider(), registerInsightProvider()

Liefert zusätzliche Tipps zu einem Konto.

API, Hooks & Einstellungen

registerApiRoute()

Registriert einen HTTP-Endpunkt unter /api/plugins/<pluginId>/... Die Plugin-Web-Seiten rufen ihn über das SDK auf.

registerStatusRoute()

Registriert unter /status eine fertige Route, die die Plugin-ID, den Status und die Einstellungen liefert. Das Scaffold nutzt sie für die Beispiel-Webseite.

registerHook()

Reagiert auf ein Ereignis, zum Beispiel nach dem Veröffentlichen eines Beitrags. Siehe Abschnitt „Hooks“.

getSettings(bereich), getSetting(key, fallback, bereich)

Liest die Einstellungen eines Bereichs (global, user oder account) oder einen einzelnen Wert. Ohne Bereichsangabe gilt global. Details im Abschnitt „Plugin-Einstellungen“.

getSettingsState(bereich), saveSettings(werte, bereich)

Liest die Einstellungen samt Schema und Zeitstempel oder speichert Werte. Beides setzt die passende settings-Berechtigung voraus.

Mehrsprachigkeit & Lebenszyklus

context.ref(key, fallback), context.createI18nRef(key, fallback)

Erstellt eine Referenz auf einen Sprachschlüssel des Plugins. ref ist die Kurzform von createI18nRef.

context.onDispose(callback)

Registriert eine Aufräumfunktion, die beim Deaktivieren des Plugins läuft, zum Beispiel für Timer.

Plugin-Typen und Beispiele

Die folgenden Beispiele zeigen, wie die wichtigsten Plugin-Typen in server/index.js registriert werden. Sie entsprechen dem, was das Scaffold erzeugt.

Texte, deren Name auf Key endet (titleKey, descriptionKey und so weiter), nehmen einen Schlüssel aus den Sprachdateien des Plugins entgegen, zum Beispiel 'adminSections.main.title'. Alternativ geht dasselbe mit context.ref('adminSections.main.title', 'Fallback-Text'). Mehr dazu im Abschnitt „Mehrsprachigkeit“.

Admin-Seite admin-page

Der einfachste Plugin-Typ. Der Inhalt der Seite kommt als Markdown aus der Sprachdatei, es braucht weder HTML noch JavaScript. Die Seite erscheint in der Seitenleiste unter Admin-Bereich in der Gruppe „Plugin-Seiten“. Gibt es zur id einen Eintrag im web/manifest.json, zeigt FediSuite stattdessen diese Web-Seite.

server/index.js
export function register(context) {
  context.registerMarkdownAdminPage({
    id: 'main',
    titleKey: 'adminSections.main.title',
    descriptionKey: 'adminSections.main.description',
    badgeKey: 'adminSections.main.badge',
    markdownKey: 'adminSections.main.markdown',
  });
}

Berechtigung: admin.sections

App-Seite app-page

Wie die Admin-Seite, aber für alle angemeldeten Nutzer*innen. Der Eintrag steht in der Seitenleiste in der Gruppe „Plugin-Bereiche“, beschriftet mit navLabelKey und dem Plugin-Namen darunter.

server/index.js
export function register(context) {
  context.registerMarkdownAppPage({
    id: 'main',
    titleKey: 'appSections.main.title',
    navLabelKey: 'appSections.main.navLabel',
    descriptionKey: 'appSections.main.description',
    markdownKey: 'appSections.main.markdown',
  });
}

Berechtigung: app.sections

Dashboard-Widget dashboard-widget

Fügt eine Karte mit Kennzahlen hinzu. Mit tab wählst Du den Tab der Seite „Analysen“ (Standard: overview), mit width die Breite (half oder full, Standard: half). Statt statusLabelKey und statusValueKey kannst Du eine Liste stats mit Einträgen aus label, value und tone übergeben (default, info, success, warning oder danger). Eine Liste items fügt Textzeilen unter den Kennzahlen hinzu.

server/index.js
export function register(context) {
  context.registerSimpleDashboardWidget({
    id: 'main',
    tab: 'interactions',   // optional, Standard: 'overview'
    titleKey: 'dashboardWidgets.main.title',
    descriptionKey: 'dashboardWidgets.main.description',
    badgeKey: 'dashboardWidgets.main.badge',
    statusLabelKey: 'dashboardWidgets.main.stats.statusLabel',
    statusValueKey: 'dashboardWidgets.main.stats.statusValue',
  });
}

Berechtigung: dashboard.widgets

Mögliche Werte für tab: overview (Übersicht), growth (Wachstum), interactions (Interaktionen), engagement (Engagement), hashtags (Hashtags), content (Inhalte), optimize (Optimieren) und tips (Tipps). Ein unbekannter Wert führt dazu, dass das Widget im Tab „Übersicht“ erscheint.

Composer-Erweiterung composer-extension

Erweitert den Composer um eigene Felder in der Karte „Plugin-Erweiterungen“. Feldtypen sind text, textarea, boolean, number und select (Passwortfelder sind im Composer nicht erlaubt). transformPost läuft, bevor der Beitrag veröffentlicht wird, und erhält { account, post, data, extension, settings }. Die Funktion gibt die zu ändernden Felder des Beitrags zurück (zum Beispiel content, title, spoiler_text, visibility, language) oder ein leeres Objekt, wenn nichts zu ändern ist. Mit instanceTypes beschränkst Du die Erweiterung auf bestimmte Plattformen.

server/index.js
export function register(context) {
  context.registerSimpleComposerExtension({
    id: 'main',
    displayNameKey: 'composer.main.displayName',
    descriptionKey: 'composer.main.description',
    fields: [
      {
        key: 'enabled',
        type: 'boolean',
        labelKey: 'composer.main.fields.enabled.label',
        descriptionKey: 'composer.main.fields.enabled.description',
        defaultValue: false,
      },
      {
        key: 'suffix',
        type: 'text',
        labelKey: 'composer.main.fields.suffix.label',
        descriptionKey: 'composer.main.fields.suffix.description',
        defaultValue: ' - sent with my plugin',
      },
    ],
    transformPost: async ({ post, data }) => {
      if (!data?.enabled || !String(data?.suffix || '').trim()) return {};
      return {
        content: `${String(post.content || '').trim()}${String(data.suffix)}`.trim(),
      };
    },
  });
}

Berechtigung: composer.extensions

Provider / Plattform-Connector provider

Verbindet eine zusätzliche Plattform mit FediSuite. beginConnection startet den Verbindungsfluss und liefert eine redirect_url. handleCallback verarbeitet die Rückkehr und liefert die Kontodaten. Der Standard-Rückkehrpfad lautet /api/providers/<providerId>/callback. Die verbundenen Konten erscheinen unter Einstellungen → Verbundene Konten im Bereich „Plugin-Konnektoren“. Dieses Beispiel entspricht dem Scaffold und ist ein Platzhalter, den Du durch Deinen echten Ablauf ersetzt:

server/index.js
export function register(context) {
  context.registerSimpleProvider({
    id: 'mein-plugin-provider',
    displayNameKey: 'providers.main.displayName',
    descriptionKey: 'providers.main.description',
    beginConnection: async ({ req }) => ({
      provider_id: 'mein-plugin-provider',
      user_id: req.user?.id || null,
      redirect_url: `${req.protocol}://${req.get('host')}/api/providers/mein-plugin-provider/callback?code=demo-code`,
      state: 'demo-state',
    }),
    handleCallback: async ({ req }) => ({
      provider_id: 'mein-plugin-provider',
      user_id: Number(req.query.user_id || 1) || 1,
      account: {
        instance_url: 'https://example.com',
        username: 'demo',
        display_name: 'Demo-Konto',
        stats_followers: 0,
        stats_following: 0,
        stats_statuses: 0,
        max_characters: 500,
        max_media_attachments: 4,
      },
      start_import: false,
    }),
    disconnect: async ({ account }) => ({
      provider_id: 'mein-plugin-provider',
      disconnected: true,
      account_id: account?.id || null,
    }),
  });
}

Berechtigung: providers

Veröffentlichen, Import und Statistiken

Ein vollständiger Provider wie das Bluesky-Plugin nutzt registerProvider und deklariert, was er kann. Das Plugin fedisuite-plugin-bluesky im Plugin-Repository ist die beste Vorlage dafür. Die Handler:

beginConnection, handleCallback

Verbindungsfluss. Bluesky nutzt eine eigene Plugin-Webseite als Formular (Handle, App-Passwort, optional PDS-Host) und nimmt die Eingaben per POST im Callback entgegen.

disconnect

Wird beim Trennen eines Kontos aufgerufen.

publishPost

Veröffentlicht einen Beitrag auf der Plattform.

importHistoricalData

Importiert den bisherigen Verlauf eines Kontos.

refreshStats, refreshPosts

Aktualisieren Kontostatistiken und Beiträge.

Neben auth (Art der Anmeldung, zum Beispiel { type: 'credentials' }) und capabilities (connect, disconnect, publish, import_historical, refresh_stats, refresh_posts und composer_profile für das Aussehen des Composers) gehören diese Handler in die Definition. Die Handler erhalten unter anderem req, pool (Datenbank), account und APP_URL.

Login-Provider auth-provider

Ergänzt die Anmeldeseite um „Oder mit Plugin anmelden“. beginLogin liefert die redirect_url zum externen Dienst, handleCallback liefert die Identität (external_subject, email, profile). Start und Rückkehr laufen standardmäßig über /api/auth/providers/<id>/start und /api/auth/providers/<id>/callback.

server/index.js
export function register(context) {
  context.registerSimpleAuthProvider({
    id: 'mein-plugin-login',
    displayNameKey: 'authProviders.main.displayName',
    descriptionKey: 'authProviders.main.description',
    beginLogin: async ({ req }) => {
      const baseUrl = `${req.protocol}://${req.get('host')}`;
      return {
        auth_provider_id: 'mein-plugin-login',
        redirect_url: `${baseUrl}/api/auth/providers/mein-plugin-login/callback?demo_email=plugin-login@example.com&demo_subject=demo-user`,
      };
    },
    handleCallback: async ({ req }) => ({
      identity: {
        external_subject: String(req.query.demo_subject || 'demo-user'),
        email: String(req.query.demo_email || 'plugin-login@example.com'),
        profile: { provider: 'mein-plugin-login', display_name: 'Demo-Login' },
      },
      tab: 'dashboard',
    }),
  });
}

Berechtigung: auth.providers

Sicherheit: Existiert in FediSuite bereits ein Konto mit der zurückgegebenen E-Mail-Adresse, meldet FediSuite diese Person an und markiert das Konto als verifiziert. Gib in identity.email daher nur Adressen zurück, die der externe Dienst tatsächlich bestätigt hat. Die Demo-Werte des Scaffolds sind nur für Tests gedacht.
Tipp-Provider insights-provider

Liefert zusätzliche Tipps zu einem Konto, die FediSuite mit den eigenen Tipps zusammenführt und sortiert. Ein Tipp braucht eine id. Dazu kommen title, text, optional reason, priority, confidence (low, medium oder high) und evidence. Statt einer festen Liste kannst Du eine Funktion generateInsights(context) angeben. Sie erhält unter anderem accountId, userId, days, timezone, settings und stats und gibt eine Liste von Tipps zurück.

server/index.js
export function register(context) {
  context.registerSimpleInsightProvider({
    id: 'mein-plugin-insights',
    displayNameKey: 'insights.main.displayName',
    tips: [
      {
        id: 'erster-tipp',
        title: 'Beispiel-Tipp',
        text: 'Dieser Tipp kommt direkt aus dem Plugin.',
        confidence: 'low',
        priority: 1,
      },
    ],
  });
}

Berechtigung: insight.providers

Plugin-API-Route registerApiRoute

Macht einen eigenen HTTP-Endpunkt unter /api/plugins/<pluginId>/... erreichbar, typischerweise für Plugin-Web-Seiten, die ihn über das SDK aufrufen. Erlaubt sind get, post, put, patch und delete. Der Pfad muss mit / beginnen und wird exakt verglichen, Platzhalter wie :id gibt es nicht (nimm Query-Parameter). Mit auth legst Du fest, wer die Route aufrufen darf: 'user' (Standard) verlangt eine angemeldete Person, 'admin' verlangt Admin-Rechte und die zusätzliche Berechtigung api.routes.admin, 'public' verlangt keine Anmeldung. Der Handler erhält unter anderem req, res, pool und plugin_id.

server/index.js
export function register(context) {
  context.registerApiRoute({
    method: 'get',
    path: '/status',        // erreichbar unter /api/plugins/<id>/status
    auth: 'user',           // 'user' (Standard), 'admin' oder 'public'
    summary: 'Gibt den Plugin-Status zurück.',
    handler: async ({ req, res }) => {
      res.json({ plugin_id: context.plugin.plugin_id, user_id: req.user?.id || null, status: 'ok' });
    },
  });
}

Berechtigung: api.routes

Hooks

Mit context.registerHook({ event, handler }) reagiert ein Plugin auf Ereignisse in FediSuite. Dafür braucht das Plugin die Berechtigung hooks und zusätzlich die Berechtigung der Gruppe, zu der das Ereignis gehört. Ein Hook darf async sein. Wirft er einen Fehler, schreibt FediSuite eine Warnung ins Log und macht weiter, ein Hook kann eine Aktion also nicht abbrechen. Hooks laufen im Prozess, in dem das Ereignis eintritt: Beiträge veröffentlicht zum Beispiel der Worker mit dem Scheduler.

server/index.js (Berechtigungen: hooks, hooks.posts)
export function register(context) {
  context.registerHook({
    event: 'post.after_publish',
    handler: async ({ post, account, publishedId }) => {
      console.log(`Beitrag ${post.id} wurde als ${publishedId} veröffentlicht.`);
    },
  });
}
server.start Berechtigung: hooks.server

Nachdem der Server zu lauschen begonnen hat.

Kontext: port, appUrl

post.before_saved Berechtigung: hooks.posts

Bevor neue Beiträge gespeichert werden.

Kontext: postPayloads, userId, accountId, scheduledAt

post.after_saved Berechtigung: hooks.posts

Nachdem Beiträge gespeichert wurden.

Kontext: posts, userId, accountId

post.before_publish Berechtigung: hooks.posts

Bevor ein Beitrag an die Plattform gesendet wird.

Kontext: post, account, mediaIds

post.after_publish Berechtigung: hooks.posts

Nachdem ein Beitrag veröffentlicht wurde.

Kontext: post, account, publishedId, parentFediverseId

post.publish_failed Berechtigung: hooks.posts

Wenn das Veröffentlichen eines geplanten Beitrags fehlgeschlagen ist.

Kontext: post, account, errorMessage

account.connected Berechtigung: hooks.accounts

Nachdem ein Konto über einen Plugin-Provider verbunden wurde.

Kontext: accountId, userId, providerId, instanceType

account.before_disconnect Berechtigung: hooks.accounts

Bevor ein Konto getrennt wird.

Kontext: accountId, userId, account

account.after_disconnect Berechtigung: hooks.accounts

Nachdem ein Konto getrennt wurde.

Kontext: accountId, userId, account

insights.generated Berechtigung: hooks.insights

Nachdem die Tipps zu einem Konto erzeugt wurden.

Kontext: accountId, userId, days, timezone, account, tips, meta

settings.updated Berechtigung: hooks.settings

Nachdem Plugin-Einstellungen gespeichert wurden.

Kontext: pluginId, scope, scopeRefId, userId, accountId, settings

Plugin-Einstellungen

Ein Plugin beschreibt seine Einstellungen im Feld settingsSchema der plugin.json. FediSuite erzeugt daraus die Eingabemasken: die Karte „Plugin-Einstellungen“ in der Plugin-Verwaltung für die globalen Werte (nur Admins) und den Abschnitt „Plugin-Einstellungen“ unter Einstellungen → Einstellungen für die Werte pro Nutzer*in. Das Preset settings legt ein Beispiel an.

plugin.json (Auszug)
"requiredPermissions": [
  "settings.global.read",
  "settings.global.write",
  "settings.user.read",
  "settings.user.write"
],
"settingsSchema": {
  "title": { "key": "settings.title", "fallback": "Plugin settings" },
  "sections": [
    {
      "id": "general",
      "title": { "key": "settings.sections.general.title", "fallback": "General" },
      "fields": [
        {
          "key": "enabled",
          "type": "boolean",
          "label": { "key": "settings.fields.enabled.label", "fallback": "Enabled" },
          "defaultValue": true
        },
        {
          "key": "welcomeMessage",
          "type": "text",
          "label": { "key": "settings.fields.welcomeMessage.label", "fallback": "Welcome message" },
          "defaultValue": "Hello from plugin settings"
        }
      ]
    }
  ]
}
Aufbau

Ein settingsSchema enthält entweder eine Liste fields oder Abschnitte (sections) mit je einer eigenen Liste fields. Jeder Schlüssel (key) darf nur einmal vorkommen.

Feldtypen

text, textarea, password, boolean, number und select. Passwortfelder gelten als geheim: Gespeicherte Werte gibt FediSuite nicht im Klartext zurück. Text darf bis zu 4096 Zeichen lang sein, textarea bis zu 65536.

Feldoptionen

label, description, placeholder, required, defaultValue, bei number zusätzlich min, max und step, bei select eine Liste options mit value und label. Texte dürfen Zeichenketten oder Sprachreferenzen der Form { "key": "…", "fallback": "…" } sein.

Bereiche

global (für die ganze Instanz, nur von Admins änderbar), user (pro Nutzer*in) und account (pro verbundenem Konto). Der wirksame Wert entsteht aus dem Standardwert, den globalen Werten und darüber den Werten von Nutzer*in und Konto.

Im Code liest Du die Werte mit getSettings() und getSetting(). Ohne Bereichsangabe gilt global. Für user und account musst Du die ID mitgeben, zum Beispiel innerhalb einer Route mit req.user.id:

server/index.js (Berechtigungen: api.routes, settings.global.read, settings.user.read)
export function register(context) {
  context.registerApiRoute({
    method: 'get',
    path: '/greeting',
    auth: 'user',
    handler: async ({ req, res }) => {
      const global = await context.getSettings();                 // globale Werte
      const message = await context.getSetting(
        'welcomeMessage',
        'Hallo',
        { scope: 'user', userId: req.user.id },                   // Wert dieser Person
      );
      res.json({ enabled: global.enabled, message });
    },
  });
}

Mit saveSettings(werte, bereich) speicherst Du Werte aus dem Plugin heraus, dafür ist die passende write-Berechtigung nötig. Speichern Admins oder Nutzer*innen Werte in der Oberfläche, löst das den Hook settings.updated aus.

Eigene Web-Seiten (web/)

Wenn Dein Plugin eine interaktive Seite mit eigenem HTML und JavaScript braucht, stellst Du Web-Dateien bereit. FediSuite lädt die HTML-Datei und bettet sie direkt in die App ein, ohne iframe. Du brauchst keinen eigenen Server. Stylesheets werden auf den Plugin-Bereich begrenzt (Selektoren wie body oder :root gelten nur dort), relative Pfade wie ./main.js oder ./style.css werden relativ zur Seite aufgelöst, und Skripte (auch type="module") werden ausgeführt. Die CSS-Begrenzung ist keine Sicherheitsgrenze.

Das web/-Verzeichnis ist optional. Für einfache Text- oder Markdown-Inhalte brauchst Du es nicht. Ein Plugin mit webEntry braucht die Berechtigung web.runtime. FediSuite liefert nur diese Dateitypen aus: html, js, mjs, css, json, svg, txt, png, jpg, jpeg, gif, webp, ico, woff, woff2 und ttf.

web/manifest.json

Diese Datei beschreibt, welche HTML-Datei zu welcher Seite gehört. frontendApiVersion muss 1 sein. Die sectionId muss exakt mit der id übereinstimmen, die Du in server/index.js vergeben hast. entry ist ein Pfad relativ zu web/, ohne führenden Schrägstrich und ohne ...

Für Admin-Seiten

web/manifest.json
{
  "frontendApiVersion": 1,
  "adminPages": [
    {
      "sectionId": "main",
      "entry": "admin-main.html"
    }
  ]
}

Für App-Seiten

web/manifest.json
{
  "frontendApiVersion": 1,
  "appPages": [
    {
      "sectionId": "main",
      "entry": "app-main.html"
    }
  ]
}
Wichtig: Die sectionId im web/manifest.json muss identisch mit der id der registrierten Seite sein (registerMarkdownAdminPage, registerAdminSection und die App-Varianten). Schon ein Unterschied in der Groß- und Kleinschreibung führt dazu, dass FediSuite die Web-Seite nicht findet und stattdessen den Markdown-Inhalt zeigt. Eine Admin-Seite gehört in adminPages, eine App-Seite in appPages.

Das Plugin-Web-SDK

Für die Kommunikation zwischen der HTML-Seite und FediSuite gibt es ein fertiges SDK. Es kümmert sich um Authentifizierung und API-Aufrufe, Du musst weder Tokens noch HTTP-Header selbst verwalten. Alle Aufrufe gehen an die Routen, die Dein Plugin mit registerApiRoute registriert hat.

Binde das SDK in Deiner HTML-Datei ein:

html
<script src="/plugin-sdk/fedisuite-plugin-web.js"></script>

Danach steht window.FediSuitePluginWeb zur Verfügung. Das Grundgerüst, das auch das Scaffold erzeugt (web/main.js):

web/main.js
const output = document.getElementById('output');

async function boot() {
  const plugin = window.FediSuitePluginWeb;

  // Auf die Initialisierung durch FediSuite warten, vor allen anderen Aufrufen
  const context = await plugin.ready();
  // context enthält: pluginId, sectionId, sectionKind ('admin' oder 'app'), language

  try {
    // Plugin-API aufrufen (Route aus registerApiRoute oder registerStatusRoute)
    const status = await plugin.get('/status');
    output.textContent = JSON.stringify({ context, status }, null, 2);
  } catch (error) {
    output.textContent = error instanceof Error ? error.message : String(error);
  }
}

boot();

Verfügbare SDK-Methoden

ready()

Liefert die Kontextdaten (pluginId, sectionId, sectionKind, language). Ruf die Methode zuerst auf, vor allen API-Aufrufen.

getContext()

Liefert denselben Kontext sofort, ohne zu warten.

get(path)

Sendet einen GET-Request an /api/plugins/<pluginId>/<path>. Der Pfad beginnt mit /.

post(path, body)

Sendet einen POST-Request. body ist ein JavaScript-Objekt und wird als JSON übertragen.

put(path, body), patch(path, body), delete(path, body)

Entsprechende Requests mit JSON-Body.

request(path, { method, body })

Die allgemeine Form hinter den Methoden oben. Ein Fehlerstatus der Route führt zu einem abgelehnten Promise mit der Fehlermeldung.

resize(), autoResize()

Nur aus Kompatibilitätsgründen vorhanden. Da die Seite direkt in die App eingebettet ist, brauchst Du beide nicht.

Mehrsprachigkeit (i18n)

FediSuite unterstützt mehrere Sprachen. Damit Dein Plugin dazu passt, lagerst Du Titel, Beschreibungen und Inhalte in Sprachdateien im Verzeichnis i18n/ aus (in der plugin.json über i18nDir eingetragen). Der Dateiname ist der Sprachcode, zum Beispiel de.json. Jede Datei ist ein JSON-Objekt, dessen Werte ausschließlich Zeichenketten (oder Listen von Zeichenketten) sind. Zahlen und Wahrheitswerte sind nicht erlaubt.

So sieht die Sprachdatei des Scaffolds für eine Admin-Seite aus:

i18n/de.json
{
  "meta": {
    "name": "Mein Plugin",
    "description": "Kurze Beschreibung meines Plugins."
  },
  "adminSections": {
    "main": {
      "title": "Mein Plugin Admin",
      "description": "Was dieses Plugin macht.",
      "badge": "Plugin",
      "markdown": "## Willkommen\n\nHier steht der Inhalt."
    }
  }
}

Im Code übergibst Du den Schlüssel, nicht den Text. Eine Referenz mit Fallback erzeugst Du bei Bedarf mit context.ref:

server/index.js
// Empfohlen: Schlüssel aus der Sprachdatei
titleKey: 'adminSections.main.title'

// Gleichwertig, mit Fallback-Text für den Fall, dass der Schlüssel fehlt
titleKey: context.ref('adminSections.main.title', 'Mein Plugin')

// Möglich, aber nicht übersetzt: ein fester Text
title: 'Mein Plugin'
  • FediSuite wählt die Sprachdatei in dieser Reihenfolge: Sprache der Oberfläche, deren Basissprache (de statt de-AT), dann Englisch. Eine en.json ist deshalb als Rückfallebene sinnvoll.
  • Fehlt der Schlüssel in der gewählten Sprachdatei, zeigt FediSuite den Fallback-Text von context.ref. Gibt es keinen, erscheint der Schlüssel selbst.
  • Der Anzeigename und die Beschreibung in der Plugin-Verwaltung kommen aus den Schlüsseln displayNameKey und descriptionKey der plugin.json.
  • Ein Text, der wie ein Schlüssel aussieht (Segmente aus Buchstaben, Ziffern, Bindestrichen und Unterstrichen, durch Punkte getrennt, ohne Leerzeichen), wird als Schlüssel behandelt. Jeder andere Text bleibt ein fester Text.

Plugin lokal testen

Liegt Dein Plugin in plugins/<id>/ und ist der Ordner eingebunden, läuft der Test immer gleich ab:

1

Plugin anlegen oder ändern, zum Beispiel mit dem Scaffold.

2

Die Container neu starten, denn FediSuite liest Plugins und ihre Manifeste nur beim Start ein: docker compose restart app worker1 worker2

3

In der Plugin-Verwaltung nachsehen, ob das Plugin erscheint. Bei einem Eintrag mit dem Status „Fehler“ nennen die aufgeklappten Details die Ursache.

4

Das Plugin aktivieren und danach die Worker neu starten (docker compose restart worker1 worker2), wenn das Plugin dort mitlaufen soll.

5

Die Seite, das Widget oder den Composer prüfen (siehe „Was ein aktives Plugin ergänzt“ in der Plugin-Verwaltung) und dabei die Logs beobachten: docker compose logs -f app

Typische Fehler & Debugging

Ob ein Plugin gestartet ist, siehst Du am Status in der Plugin-Verwaltung. Bei „Fehler“ steht die Ursache in den aufgeklappten Details. Die Container-Logs nennen sie nicht. Die folgenden Meldungen kommen dort am häufigsten vor:

Plugin manifest is missing required field "…"

Ein Pflichtfeld fehlt in der plugin.json.

Plugin API version X is not compatible with supported version 1.

pluginApiVersion passt nicht zur Version, die FediSuite erwartet.

Plugin requires at least app version …

minAppVersion oder maxAppVersion schließt die laufende FediSuite-Version aus.

Plugin entry file not found: …

serverEntry, webEntry oder i18nDir zeigt auf eine Datei, die es nicht gibt.

Plugin "…" does not export a register(context) function.

server/index.js exportiert keine Funktion register.

Plugin "…" must declare permission "…" to …

Das Plugin registriert etwas, ohne die Berechtigung in requiredPermissions zu nennen.

Plugin web manifest … / frontendApiVersion …

Das web/manifest.json ist ungültig, hat eine andere frontendApiVersion als 1, einen unsicheren Pfad oder doppelte sectionId-Werte.

Plugin wird nicht erkannt

  • Liegt plugin.json direkt im Plugin-Ordner (nicht eine Ebene tiefer)?
  • Ist der plugins/-Ordner als Volume nach /app/plugins eingebunden?
  • Wurden die Container nach der Änderung neu gestartet?
  • Stehen PLUGINS_ENABLED und PLUGIN_SCAN_ON_START nicht auf false?
Diagnosebefehle
ls -la plugins/mein-plugin/
docker compose config | grep plugins
docker compose exec app ls /app/plugins
docker compose restart app worker1 worker2

Plugin startet nicht (Status „Fehler“)

  • Stimmt pluginApiVersion in plugin.json? Derzeit muss der Wert 1 sein.
  • Existiert die Datei, auf die serverEntry zeigt, und exportiert sie eine Funktion register?
  • Sind alle Berechtigungen in requiredPermissions gesetzt, die Deine register-Funktion braucht?
  • Ist das JSON in plugin.json und in den Sprachdateien gültig, und enthalten die Sprachdateien nur Zeichenketten?
Diagnosebefehle
docker compose exec app node --check /app/plugins/mein-plugin/server/index.js
python3 -m json.tool plugins/mein-plugin/plugin.json

Seite oder Widget erscheint nicht in der App

  • Wurde die Seite mit der passenden Methode registriert (Admin-Seite oder App-Seite)?
  • Ist die passende Berechtigung in requiredPermissions eingetragen?
  • Ist das Plugin in der Plugin-Verwaltung aktiviert und hat den Status „Gestartet“?
  • Hast Du die Seite nach dem Aktivieren neu geladen?
Diagnosebefehle
docker compose logs app

Plugin-Web-Seite bleibt leer oder lädt nicht

  • Ist webEntry in plugin.json gesetzt und die Berechtigung web.runtime vorhanden?
  • Ist web/manifest.json gültiges JSON mit frontendApiVersion 1?
  • Stimmt die sectionId in web/manifest.json exakt mit der id der registrierten Seite überein?
  • Steht die Seite in der richtigen Liste (adminPages oder appPages)?
  • Ist das SDK eingebunden: <script src="/plugin-sdk/fedisuite-plugin-web.js">?
  • Wird await window.FediSuitePluginWeb.ready() vor den API-Aufrufen aufgerufen?
  • Sind Asset-Pfade relativ zum web/-Verzeichnis (./main.js, ./style.css)?
  • Existiert die Route, die die Seite aufruft, und erlaubt ihr auth-Wert den Zugriff?
Diagnosebefehle
docker compose logs app
Weiterführende Referenz: Im Plugin-Repository liegen die ausführliche Anleitung für Plugin-Autor*innen auf Deutsch (PLUGIN-AUTHORING.de.md (öffnet in neuem Tab)) und eine kurze englische Fassung (PLUGIN-AUTHORING.md (öffnet in neuem Tab)). Bei Abweichungen gilt der Code der Plugin-Laufzeit im Hauptrepository im Verzeichnis server/plugins (öffnet in neuem Tab).