WordPress.org-Plugin-Richtlinien: woran Einreichungen scheitern

Management Summary
Das Verzeichnis von WordPress.org prüft mehr als die Funktionsfähigkeit eines Plugins, also einer Erweiterung für Websites. Auch Lizenzen, ehrliche Beschreibungen, Bezahlangebote und Datenübertragungen müssen zu den Verzeichnisregeln passen. Klare Entscheidungen über Statistiken und Herstellerlinks sichern die Kontrolle über die Website. Die Übersicht erklärt, warum versteckte Übertragungen oder Bezahlsperren für enthaltene Funktionen zur Ablehnung führen können und automatische Tests die Teamprüfung nicht ersetzen.
Inhalt
- Die 18 Regeln im Überblick
- Lizenz und lesbarer Code
- Was kostenlos sein muss und was bezahlt sein darf
- Externe Anfragen nur nach einer klaren Entscheidung
- Beispiel: Lesezeit, Statistikknopf und Herstellerlink
- Die Übertragung im Readme erklären
- Name und dauerhafte Adresse
- Von der Einreichung zur Veröffentlichung
- Was sich daraus nicht ableiten lässt
- Fragen und Antworten
- Quellen
Ein funktionierendes Plugin kann trotzdem an der Aufnahme in das Verzeichnis von WordPress.org scheitern. Neben dem Code prüft das Team zum Beispiel die Lizenz, externe Datenübertragungen und den Namen. Ein versteckter Statistikversand oder eine gesperrte Funktion im kostenlosen Paket kann deshalb ebenso relevant sein wie ein Programmfehler.
Geprüft am 25. Oktober 2026. Die Detailed Plugin Guidelines enthalten 18 Regeln. Die folgende Übersicht fasst zusammen, was diese Regeln für ein fertiges Plugin bedeuten. Das Beispiel zeigt zwei getrennte Entscheidungen: Statistikversand erlauben und einen Link zum Hersteller auf der Website anzeigen. Beides bleibt zunächst ausgeschaltet.
Plugin Check, Sicherheitsfunktionen und Readme und Plugin-Header werden in eigenen Beiträgen erklärt. Beispiele fertiger Plugins sind Lite Page Cache und WP Admin Captcha.
Die 18 Regeln im Überblick
Die Nummern entsprechen der offiziellen Richtlinienseite. Die Gruppen zeigen zusammengehörige Themen, keine Rangliste der Ablehnungsgründe.
| Thema | Regeln | Praktische Folge |
|---|---|---|
| Lizenz und Quelltext | 1, 4, 13 | Alle Dateien brauchen passende Lizenzen. Lesbarer Quelltext muss erreichbar sein. Bereits in WordPress enthaltene Bibliotheken werden nicht nochmals mitgeliefert. |
| Kostenpflichtige Angebote | 5, 6, 8 | Im Paket enthaltene Funktionen dürfen nicht durch Zahlung freigeschaltet werden. Dokumentierte Dienste sind erlaubt; für externen Code gelten besondere Grenzen. |
| Zustimmung und Oberfläche | 7, 10, 11 | Externe Kontakte müssen nachvollziehbar und autorisiert sein. Herstellerlinks sind freiwillig. Werbung darf den Verwaltungsbereich nicht vereinnahmen. |
| Veröffentlichung und Verhalten | 2, 3, 9, 12, 14–18 | Vollständige, aktuelle Pakete, ehrliche Angaben, passende Namen und Versionsnummern; keine Spam-Beschreibungen oder unnötigen SVN-Änderungen. |
Die Verantwortung umfasst auch eingebundene Bibliotheken und die Nutzungsbedingungen fremder Dienste. Das Team kann Regeln ändern, Ausnahmen gewähren oder ein Plugin entfernen. Die Übersicht ersetzt deshalb nicht die aktuelle Richtlinienseite.
Lizenz und lesbarer Code
GPL-kompatibel bedeutet: Die Lizenz muss mit der GNU General Public License vereinbar sein. WordPress empfiehlt „GPLv2 or later“. Das gilt nicht nur für selbst geschriebenen PHP-Code, sondern auch für mitgelieferte Bilder, Datendateien und fremde Bibliotheken. Widersprüchliche Angaben in Plugin-Header und Readme müssen vor der Einreichung geklärt werden.
Minifizierte Dateien sind für die Auslieferung verkleinerte Programmdateien. Der lesbare Quelltext und die verwendeten Bauwerkzeuge müssen öffentlich zugänglich sein, entweder im Paket oder über einen Link im Readme. Bei Composer gehört die composer.json zur nachvollziehbaren Beschreibung. Bibliotheken wie jQuery oder PHPMailer bringt WordPress bereits mit; ein Plugin soll diese vorhandenen Fassungen nutzen. Unbenötigte Test-, Demo- und Entwicklungsordner gehören nicht ins Veröffentlichungspaket.
Was kostenlos sein muss und was bezahlt sein darf
Ein Plugin aus dem Verzeichnis darf enthaltene Funktionen nicht erst nach Bezahlung freischalten oder nach einer Testphase abschalten. Das ist mit Trialware gemeint. Ein kostenpflichtiger externer Dienst ist dagegen zulässig, wenn der Dienst eine tatsächliche Leistung erbringt und im Readme verständlich beschrieben wird. Eine reine Lizenzprüfung für ansonsten vollständig lokalen Code reicht dafür nicht.
Auch ein separates kostenpflichtiges Zusatzplugin außerhalb von WordPress.org ist laut Regel 5 möglich. Der Premium-Code gehört dann nicht in das kostenlose Paket. Das Verzeichnis-Plugin darf die eigene Premium-Fassung nicht selbst herunterladen und installieren. Damit bleibt der Unterschied zwischen freier Funktion, externem Dienst und zusätzlichem Produkt erkennbar.
Regel 8 verbietet externen Code nicht pauschal. Code aus dokumentierten Diensten ist unter den genannten Sicherheitsbedingungen zulässig. Ohne solchen Dienstbezug gehören JavaScript und CSS ins lokale Paket; fremde Updatequellen, die Installation der Premium-Fassung und per iframe eingebundene fremde Verwaltungsseiten sind problematisch. Die Richtlinie nennt außerdem eine Ausnahme für Verwaltungsdienste, die Software über die eigene Domain statt innerhalb des WordPress-Dashboards verteilen.
Hinweise auf ein Upgrade dürfen zum jeweiligen Arbeitsablauf passen oder auf der Plugin-Einstellungsseite stehen. Hinweise im gesamten Verwaltungsbereich müssen sich schließen lassen. Das Plugin darf weder Funktionen vortäuschen noch den Eindruck erwecken, bereits enthaltene Funktionen verlangten eine zusätzliche Zahlung.
Externe Anfragen nur nach einer klaren Entscheidung
Ein Opt-in ist eine aktive Zustimmung, etwa ein zunächst leeres Kontrollkästchen. Regel 7 verlangt eine ausdrückliche, autorisierte Zustimmung zu externen Kontakten und eine Beschreibung der Datennutzung. Die Dienstausnahme betrifft den erkennbaren Zweck eines eingerichteten Dienstes: Installation, Aktivierung, Registrierung und Konfiguration können nach der Verzeichnisregel die dafür nötige Zustimmung ausdrücken. Das erlaubt keine beliebige zusätzliche Statistikübertragung und ist keine allgemeine Aussage zur rechtlichen Zulässigkeit jeder Datenverarbeitung.
Ein Herstellerlink auf der öffentlich sichtbaren Website ist eine eigene Entscheidung. Nach Regel 10 muss der Link zunächst aus sein und darf keine Voraussetzung für die Plugin-Funktion werden. Eine versteckte Klausel in Nutzungsbedingungen ersetzt diese Auswahl nicht.
Beispiel: Lesezeit, Statistikknopf und Herstellerlink
Das Beispiel berechnet die Lesezeit lokal mit der groben Annahme von 200 durch Leerraum getrennten Wörtern pro Minute. Die Schätzung passt nicht gleich gut zu allen Sprachen und zählt mindestens eine Minute. Zwei Einstellungen erlauben unabhängig voneinander den Statistikversand und den Herstellerlink.
Das Speichern der Statistikoption sendet noch nichts. Erst der separate Knopf „Send statistics once“ löst einen Versuch aus. Der gespeicherte Haken, die Berechtigung manage_options und die Formularprüfung müssen dann weiterhin gültig sein. Die sogenannte Nonce erschwert fremd ausgelöste Formularanfragen; die Berechtigungsprüfung entscheidet zusätzlich, ob das Konto die Aktion ausführen darf.
stats.example.com ist ein Platzhalter. Vor einem wirklichen Einsatz braucht das Beispiel eine tatsächlich betriebene Zieladresse mit passenden Informationen zur Datennutzung. Der Code bleibt während der Demonstration außerhalb einer produktiven Website.
/**
* Plugin Name: Reading Time Badge
* Description: Shows the estimated reading time above posts. Usage statistics and a credit link are opt-in.
* Version: 1.0.0
* Requires at least: 6.5
* Requires PHP: 7.4
* License: GPLv2 or later
* License URI: https://www.gnu.org/licenses/gpl-2.0.html
* Text Domain: reading-time-badge
*
* @package ReadingTimeBadge
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
define( 'RTBADGE_VERSION', '1.0.0' );
define( 'RTBADGE_STATS_URL', 'https://stats.example.com/v1/ping' );
/**
* Returns the settings merged with defaults. Every outgoing feature is off by default.
*
* @return array
*/
function rtbadge_get_settings() {
$defaults = array(
'share_stats' => false,
'show_credit' => false,
);
return wp_parse_args( (array) get_option( 'rtbadge_settings', array() ), $defaults );
}
/**
* Stores exactly two booleans, whatever the form sends.
*
* @param mixed $input Raw option value.
* @return array
*/
function rtbadge_sanitize_settings( $input ) {
$input = is_array( $input ) ? $input : array();
return array(
'share_stats' => isset( $input['share_stats'] ) && in_array( $input['share_stats'], array( true, '1' ), true ),
'show_credit' => isset( $input['show_credit'] ) && in_array( $input['show_credit'], array( true, '1' ), true ),
);
}
add_action( 'admin_init', 'rtbadge_register_settings' );
/**
* Registers the option with a sanitize callback.
*/
function rtbadge_register_settings() {
register_setting(
'rtbadge',
'rtbadge_settings',
array(
'type' => 'array',
'sanitize_callback' => 'rtbadge_sanitize_settings',
'default' => array(
'share_stats' => false,
'show_credit' => false,
),
)
);
}
add_action( 'admin_menu', 'rtbadge_add_settings_page' );
/**
* Adds the settings page below Settings.
*/
function rtbadge_add_settings_page() {
add_options_page(
__( 'Reading Time Badge', 'reading-time-badge' ),
__( 'Reading Time Badge', 'reading-time-badge' ),
'manage_options',
'rtbadge',
'rtbadge_render_settings_page'
);
}
/**
* Renders two unchecked-by-default opt-in checkboxes.
*/
function rtbadge_render_settings_page() {
if ( ! current_user_can( 'manage_options' ) ) {
return;
}
$settings = rtbadge_get_settings();
<div class="wrap">
<h1> echo esc_html( get_admin_page_title() ); </h1>
<form action="options.php" method="post">
settings_fields( 'rtbadge' );
<p>
<label>
<input type="checkbox" name="rtbadge_settings[share_stats]" value="1" checked( $settings['share_stats'] ); >
esc_html_e( 'Allow sending plugin, WordPress and PHP versions to stats.example.com with the separate Send statistics button.', 'reading-time-badge' );
</label>
</p>
<p>
<label>
<input type="checkbox" name="rtbadge_settings[show_credit]" value="1" checked( $settings['show_credit'] ); >
esc_html_e( 'Show a link to the plugin website next to the reading time on the public site.', 'reading-time-badge' );
</label>
</p>
submit_button();
</form>
<form action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>" method="post">
<input type="hidden" name="action" value="rtbadge_send_stats">
wp_nonce_field( 'rtbadge_send_stats' );
submit_button( __( 'Send statistics once', 'reading-time-badge' ), 'secondary' );
</form>
</div>
}
add_action( 'admin_post_rtbadge_send_stats', 'rtbadge_send_stats' );
/**
* Sends one request after a deliberate action by an authorized administrator.
*/
function rtbadge_send_stats() {
if ( ! isset( $_SERVER['REQUEST_METHOD'] ) || 'POST' !== $_SERVER['REQUEST_METHOD'] ) {
wp_die( esc_html__( 'POST is required.', 'reading-time-badge' ) );
}
if ( ! current_user_can( 'manage_options' ) ) {
wp_die( esc_html__( 'Permission denied.', 'reading-time-badge' ) );
}
check_admin_referer( 'rtbadge_send_stats' );
$settings = rtbadge_get_settings();
if ( true !== $settings['share_stats'] ) {
wp_die( esc_html__( 'Statistics sharing is disabled. Save the setting before sending.', 'reading-time-badge' ) );
}
$response = wp_safe_remote_post(
RTBADGE_STATS_URL,
array(
'timeout' => 5,
'redirection' => 0,
'user-agent' => 'reading-time-badge/' . RTBADGE_VERSION,
'body' => array(
'plugin_version' => RTBADGE_VERSION,
'wp_version' => get_bloginfo( 'version' ),
'php_version' => PHP_VERSION,
),
)
);
if ( is_wp_error( $response ) ) {
wp_die( esc_html__( 'The request failed. No automatic retry is scheduled.', 'reading-time-badge' ) );
}
$status = wp_remote_retrieve_response_code( $response );
if ( $status < 200 || $status >= 300 ) {
wp_die( esc_html__( 'The service did not return a successful HTTP response.', 'reading-time-badge' ) );
}
wp_die( esc_html__( 'The service returned a successful HTTP response. This does not verify storage.', 'reading-time-badge' ) );
}
add_filter( 'the_content', 'rtbadge_prepend_reading_time' );
/**
* Prepends the reading time. The credit link appears only after opt-in.
*
* @param string $content Post content.
* @return string
*/
function rtbadge_prepend_reading_time( $content ) {
if ( ! is_singular( 'post' ) || ! in_the_loop() || ! is_main_query() ) {
return $content;
}
$words = preg_split( '/\s+/u', trim( wp_strip_all_tags( $content ) ), -1, PREG_SPLIT_NO_EMPTY );
if ( false === $words ) {
return $content;
}
$minutes = max( 1, (int) ceil( count( $words ) / 200 ) ); // Assumes 200 words per minute.
$badge = '<p class="rtbadge">' . esc_html(
sprintf(
/* translators: %d: estimated reading time in minutes */
_n( '%d minute read', '%d minutes read', $minutes, 'reading-time-badge' ),
$minutes
)
);
$settings = rtbadge_get_settings();
if ( true === $settings['show_credit'] ) {
$badge .= ' · <a href="' . esc_url( 'https://example.com/reading-time-badge/' ) . '">'
. esc_html__( 'Reading Time Badge', 'reading-time-badge' ) . '</a>';
}
return $badge . '</p>' . $content;
}Der Versand enthält drei Versionsangaben. Ein eigener User-Agent ersetzt die standardmäßige Kennung der WordPress-HTTP-API, die auch die Website-Adresse enthält. Die Quell-IP des Servers bleibt beim Empfänger sichtbar. Weiterleitungen sind ausgeschaltet. Eine HTTP-Antwort im Bereich 200–299 bestätigt nur die erfolgreiche Antwort des Servers, nicht die Speicherung der Daten. Ein erneuter Klick kann einen weiteren Versuch auslösen; automatische Wiederholungen oder eine Wochensperre enthält das Beispiel nicht.
Die beiden Formulare haben getrennte Aufgaben: „Änderungen speichern“ speichert die Auswahl, der zweite Knopf sendet einmal. Vor dem Abschalten weiterer Übertragungen muss auch das entfernte Häkchen gespeichert werden. Die Ausgabe auf einer Bestätigungsseite ist bewusst einfach gehalten; ein fertiges Plugin kann stattdessen einen passenden Hinweis auf der Einstellungsseite anzeigen.
Die Übertragung im Readme erklären
Die Dokumentation nennt Zweck, Zeitpunkt, Daten und Empfänger. Links zu Dienst, Bedingungen und Datenschutzhinweisen müssen beim fertigen Plugin auf echte Seiten zeigen. Der folgende englische Abschnitt ist ein Muster für denselben manuellen Ablauf:
== Privacy and external services ==
Reading Time Badge calculates reading time locally and works without an external service.
Statistics sharing and the public credit link are disabled by default.
Saving the statistics opt-in does not send a request. After that setting is saved,
an administrator can press "Send statistics once" to send one request to
https://stats.example.com/v1/ping. Each additional click can send another request.
Clearing the opt-in and saving the setting prevents further sends by this feature.
The example request body contains plugin, WordPress and PHP versions.
The example does not add site content or the site URL to that body or its custom user agent.
The receiving service can see the server's source IP address.
No automatic retry or weekly schedule is implemented.
The example addresses below are placeholders and must be replaced with actual service details.
* Service: https://stats.example.com/
* Terms of use: https://stats.example.com/terms/
* Privacy policy: https://stats.example.com/privacy/Name und dauerhafte Adresse
Der Slug ist der kurze Name in der Adresse, zum Beispiel wordpress.org/plugins/reading-time-badge. Daraus ergeben sich auch Plugin-Ordner, SVN-Adresse und Text Domain für Übersetzungen. Der anfängliche Vorschlag stammt aus Plugin Name im Hauptdatei-Header.
Die offiziellen Seiten beschreiben Namenskonflikte unterschiedlich: Die Einreichungsseite nennt eine angehängte Zahl, die Entwickler-FAQ eine Warnung beim Upload. Maßgeblich ist deshalb der tatsächlich bestätigte Slug. Eine eigenständige Namenswahl vermeidet den Konflikt, statt sich auf eine automatische Nummer zu verlassen.
Eine eigene Slug-Änderung ist laut Einreichungsseite einmal vor Beginn der Prüfung möglich. Während der Prüfung erfolgt die Klärung über den bestehenden Mailverlauf. Vor der Freigabe kann das Team noch Änderungen vornehmen; nach der Freigabe lässt sich der Slug nicht mehr umbenennen. Der angezeigte Plugin-Name bleibt dagegen änderbar.
Eine fremde Marke darf nicht allein oder am Anfang des Slugs stehen, sofern keine nachgewiesene Inhaberschaft oder berechtigte Vertretung vorliegt. „Delivery Dates for WooCommerce“ zeigt die Form Funktion plus Marke; „WooCommerce Delivery Dates“ kann dagegen eine offizielle Herkunft nahelegen. Auch die erste Form ist keine automatische rechtliche Freigabe für jede konkrete Namensverwendung.
/**
* Plugin Name: Delivery Dates for WooCommerce
* Description: Lets customers pick a delivery date at checkout.
* Version: 1.0.0
* License: GPLv2 or later
* Text Domain: delivery-dates-for-woocommerce
*
* @package DeliveryDatesForWooCommerce
*/
// Main file: delivery-dates-for-woocommerce/delivery-dates-for-woocommerce.php
// Slug proposed on submission: delivery-dates-for-woocommerce
if ( ! defined( 'ABSPATH' ) ) {
exit;
}Von der Einreichung zur Veröffentlichung
Das ZIP muss sich als Plugin installieren lassen. Die FAQ nennt 10 MB als Größenlimit; unnötige Entwicklungsdateien vergrößern das Paket. Für Konten mit Einreichungs- oder Commit-Rechten ist seit Oktober 2024 Zwei-Faktor-Authentifizierung vorgeschrieben. Das bedeutet einen zusätzlichen Anmeldefaktor neben dem Passwort. Für SVN steht ein gesondertes Passwort zur Verfügung.
Die Ankündigung vom 1. Oktober 2024 beschreibt Plugin Check als Vorprüfung: Fehler in der Kategorie „Plugin Repo“ blockieren die Einreichung. Das ist der dokumentierte Ausgangspunkt; der Umfang der aktuellen Prüfungen kann sich weiterentwickeln. Ein fehlerfreier Durchlauf ersetzt die anschließende Prüfung durch das Team nicht.
Beanstandungen werden im selben Mailverlauf bearbeitet. Nach einer Ablehnung wegen eines nach drei Monaten unvollständigen Reviews sieht die FAQ eine neue Einreichung und eine Antwort auf die alte Mail vor. Bei anderen Ablehnungsgründen soll keine neue Einreichung erfolgen, sondern die Klärung im bestehenden Verlauf.
Nach der Freigabe wird SVN zugänglich. SVN ist hier ein Speicher für veröffentlichungsfertige Versionen, nicht für jeden Zwischenschritt der Entwicklung. trunk/ enthält den aktuellen Stand, tags/ benannte Versionen und assets/ Verzeichnisbilder. Ein Commit überträgt Änderungen ins Repository und kann die Veröffentlichung auslösen.
Davon getrennt ist die Verteilung über die Update-API. Die Ankündigung vom 9. September 2026 nennt dafür sechs Stunden Wartezeit und eine automatische Sicherheitsprüfung. Hohe Risikowerte können eine Version sperren. Diese Verteilung ist nicht gleichbedeutend mit dem bloßen Speichern von Dateien in SVN.

Was sich daraus nicht ableiten lässt
Historische Warteschlangenzahlen beschreiben einen Bestand, keine persönliche Wartezeit. Eine kleine Zahl ungeprüfter Einreichungen garantiert keinen schnellen Abschluss. Die Einreichungsseite nennt als Ziel fünf Werktage und einen Rahmen von einem bis zehn Tagen, abhängig von der Komplexität. Diese Angaben sind keine Terminzusage.
Das gezeigte Plugin wurde isoliert auf Zugriffsprüfung, Zustimmung, Versandfehler und Ausgabe geprüft. Solche Tests ersetzen weder einen vollständigen WordPress-Lauf noch Plugin Check oder die Entscheidung des Verzeichnisteams.
Fragen und Antworten
Darf ein kostenloses Plugin auf ein Pro-Angebot hinweisen?
Ja, in den beschriebenen Grenzen der Regeln 5, 8 und 11. Die im kostenlosen Paket enthaltenen Funktionen bleiben nutzbar. Ein separates Zusatzplugin oder ein tatsächlicher externer Dienst kann kostenpflichtig sein.
Reicht ein fehlerfreier Plugin-Check-Lauf?
Nein. Lizenz, Dienstzweck, Darstellung und Namensrechte werden damit nicht abschließend beurteilt. Die Prüfung durch das Team bleibt erforderlich.
Ist ein fehlender Versandfehler ein Nachweis der Speicherung?
Nein. Selbst eine erfolgreiche HTTP-Antwort belegt nicht, dass der Empfänger die Daten dauerhaft gespeichert hat. Ohne Zustimmung oder ohne gültige Formular- und Berechtigungsprüfung sendet das Beispiel keine Anfrage.
Wie viele Plugins dürfen gleichzeitig in Prüfung sein?
Die Entwickler-FAQ nennt normalerweise eine Einreichung. Für Autoren mit mehr als einer Million aktiven Plugin-Installationen gilt ein erweitertes Limit von bis zu zehn. Zusätzliche Konten zur Umgehung des Limits sind nicht zulässig.
Quellen
- Detailed Plugin Guidelines
- Common issues
- Plugin Developer FAQ
- Plugin submission
- Planning, Submitting, and Maintaining Plugins
- How to Use Subversion
- Plugin Check and 2FA Now Mandatory For New Plugin Submissions
- Plugins Team: 28 Sep 2026
- Plugins Team: 7 Sep 2026
- Update on the status of the team – June 2026
- A Year in the Plugins Team – 2025
- Automated security review for plugin releases
- class-wp-http.php (Standard-User-Agent)
- WordPress Transients API
- WordPress: wp_safe_remote_post
- WordPress: Nonces