Plugin Check lokal und in der CI: Befunde vor dem Review

Inhalt
Jedes Plugin, das im Verzeichnis von WordPress.org eingereicht wird, durchläuft zuerst einen automatischen Scan, erst danach liest jemand aus dem Plugins Team den Code. Ein Teil dessen, was im Review beanstandet wird, lässt sich maschinell finden: ein fehlender Lizenz-Header, ungeschützte Ausgaben, ein Skript von einem öffentlichen CDN, eine Text Domain, die nicht zum Slug passt. Plugin Check, kurz PCP, ist das Werkzeug, das WordPress.org genau dafür veröffentlicht. Laut Beschreibung führt es die meisten Prüfungen aus, die auch bei neuen Einreichungen laufen, und zwar auf jeder WordPress-Installation, bevor irgendetwas hochgeladen wird.
Dieser Beitrag zeigt Plugin Check 2.1.0 an drei Stellen: in der Oberfläche im Backend, auf der Kommandozeile mit WP-CLI und in einem GitHub-Actions-Workflow mit der offiziellen Action. Als Prüfobjekt dient ein kleines Plugin mit absichtlich eingebauten Fehlern. Zu jedem Fehler nennt der Text die Prüfung und den Ergebniscode, den ein Lauf von Plugin Check 2.1.0 unter WordPress 7.1.2 meldet, und zeigt danach die korrigierte Fassung.
Was Plugin Check prüft
Aktuell im Verzeichnis ist Version 2.1.0 vom 16. August 2026. Vorausgesetzt werden WordPress 6.3 und PHP 7.4; die Plugin-Seite nennt „Tested up to“ 7.0.6, während die aktuelle WordPress-Version 7.1.2 lautet. Neu in 2.1.0 ist unter anderem eine Prüfung auf Änderungen an der PHP-Fehlerausgabe zur Laufzeit.
Im Quelltext von 2.1.0 registriert die Klasse Default_Check_Repository 34 Prüfungen. Jede trägt einen Slug wie late_escaping und gehört zu einer oder mehreren Kategorien. Es gibt zwei Arten. Statische Prüfungen lesen den Code, ohne ihn auszuführen, meist über PHP_CodeSniffer-Sniffs aus den WordPress Coding Standards oder aus dem eigenen Sniff-Satz von Plugin Check, teils über eigene Logik. Laufzeitprüfungen führen das Plugin, das dafür aktiviert sein muss, mit einem abgetrennten Satz Datenbanktabellen aus und beobachten, was es tut; in 2.1.0 sind das die fünf Performance-Prüfungen zu Reichweite und Größe von Skripten und Stylesheets sowie zur Ladestrategie.
| Kategorie (Slug) | Beispiele für Prüfungen in 2.1.0 |
|---|---|
General (general) |
i18n_usage, php_error_reporting, ai_provider |
Plugin Repo (plugin_repo) |
plugin_readme, plugin_header_fields, file_type, offloading_files, setting_sanitization, prefixing, plugin_review_phpcs, trademarks |
Security (security) |
late_escaping, direct_file_access, safe_redirect, direct_db_queries, direct_db |
Performance (performance) |
enqueued_scripts_in_footer, performant_wp_query_params, enqueued_scripts_scope, non_blocking_scripts |
Accessibility (accessibility) |
keine: Die Kategorie ist definiert, aber keine mitgelieferte Prüfung aus 2.1.0 ist ihr zugeordnet |
Alle in 2.1.0 registrierten Security-Prüfungen tragen zusätzlich die Kategorie plugin_repo. Ein Lauf nur über Plugin Repo deckt also Escaping und direkten Dateizugriff bereits ab. Das ist wichtig, weil die FAQ des Plugins festhält, dass ein Plugin für die Aufnahme ins Verzeichnis in der Regel alle Prüfungen der Kategorie Plugin Repo bestehen muss. Die übrigen Kategorien gelten als zusätzlich.
Jedes Ergebnis ist entweder ERROR oder WARNING und hat eine Schwere (Severity); Standard ist 5, eine fehlende Lizenz im Plugin-Header meldet die Prüfung mit 9. Fehler stehen für Verstöße gegen Anforderungen des Verzeichnisses. Warnungen markieren Code, der oft, aber nicht immer ein Problem ist: $_GET ohne Nonce zu lesen ist bei einem harmlosen Anzeigeparameter unkritisch und in einem Formular-Handler eine echte Lücke. Den Typ legt die jeweilige Prüfung fest. Das PHPCS-Regelwerk von Plugin Check stuft einige Sniffs bewusst auf Warnungen herunter, etwa die Nonce-Prüfung und die Bereinigung von Eingaben.
Lokal ausführen: Backend und WP-CLI
Plugin Check wird wie jedes andere Plugin installiert. Das README des Projekts rät vom Einsatz auf Produktivseiten ab, weil Laufzeitprüfungen das geprüfte Plugin tatsächlich ausführen. Nach der Aktivierung steht unter Werkzeuge → Plugin Check eine Seite bereit, die nur Nutzer mit dem Recht zur Plugin-Verwaltung sehen. Dort gibt es eine Plugin-Auswahl sowie Checkboxen für Kategorien und Ergebnistypen. Die Ergebnisse erscheinen je Datei mit Zeile, Spalte, Typ, Code und Meldung; seit Version 1.8.0 lassen sie sich als CSV, JSON oder Markdown exportieren.
Für wiederholte Läufe ist der WP-CLI-Befehl praktischer. Als Argument akzeptiert er einen Plugin-Slug, einen Pfad oder die URL einer ZIP-Datei. Standardmäßig laufen nur statische Prüfungen. Für Laufzeitprüfungen beschreibt das README einen Umweg: --require lädt die Datei cli.php von Plugin Check, bevor WordPress startet. Außerdem muss das geprüfte Plugin aktiv sein; bei einem inaktiven Plugin lässt Plugin Check die Laufzeitprüfungen ohne Hinweis weg, und --checks=enqueued_scripts_scope endet dann mit „does not exist“.
# Install and activate Plugin Check (2.1.0 at the time of writing).
wp plugin install plugin-check --activate
# All static checks for wp-content/plugins/lw-hello-banner.
wp plugin check lw-hello-banner
# Include runtime checks (plugin must be active): load cli.php before WordPress boots.
wp plugin check lw-hello-banner --require=./wp-content/plugins/plugin-check/cli.php
# Limit the run to categories or to single checks.
wp plugin check lw-hello-banner --categories=plugin_repo,security
wp plugin check lw-hello-banner --checks=late_escaping,i18n_usage
# Errors only, as one JSON array for scripts.
wp plugin check lw-hello-banner --ignore-warnings --format=strict-json
# Show result codes, then ignore one specific code.
wp plugin check lw-hello-banner --format=csv --fields=code,message
wp plugin check lw-hello-banner --ignore-codes=textdomain_mismatch
# Checks for an update of an existing directory plugin.
wp plugin check lw-hello-banner --mode=update
# What is available in the installed version.
wp plugin list-checks --format=csv
wp plugin list-check-categories
Die wichtigsten Optionen von wp plugin check in Version 2.1.0:
| Option | Wirkung |
|---|---|
--checks=, --exclude-checks= |
nur die genannten Prüfungen ausführen oder sie auslassen |
--categories= |
Lauf auf Kategorien beschränken, kommagetrennt |
--ignore-codes= |
einzelne Ergebniscodes ausblenden, etwa textdomain_mismatch |
--ignore-warnings, --ignore-errors |
einen der beiden Ergebnistypen ausblenden |
--format= |
table (Standard), csv, json, ctrf sowie strict-table, strict-csv, strict-json, strict-ctrf |
--fields= |
Spalten wählen, zum Beispiel code,message |
--exclude-directories=, --exclude-files= |
Pfade bei dateibasierten Scans überspringen; .git, vendor, vendor_prefixed, vendor-prefixed und node_modules sind ohnehin ausgenommen |
--severity=, --error-severity=, --warning-severity= |
nur Ergebnisse ab einer bestimmten Schwere zeigen |
--mode= |
new (Standard) oder update; im Update-Modus ist ein veraltetes „Tested up to“ eine Warnung statt eines Fehlers |
--slug= |
den Slug überschreiben, gegen den Text Domain und Readme verglichen werden |
Prüfungs-Slugs und Ergebniscodes sind zweierlei. --checks=i18n_usage wählt eine Prüfung aus, --ignore-codes=WordPress.WP.I18n.TextDomainMismatch blendet einen einzelnen Befund daraus aus. Zum Nachschlagen der Codes empfiehlt die CLI-Dokumentation --format=csv --fields=code,message. Für Skripte zählt noch ein Detail: Im Quelltext von 2.1.0 gibt der Befehl die Befunde aus, setzt ihretwegen aber keinen Fehler-Exitcode. Ein Shell-Skript, das bei Befunden scheitern soll, muss die Ausgabe also selbst auswerten; im Test meldete der Befehl beim fehlerhaften Beispiel zwölf Fehler und endete trotzdem mit Exitcode 0. Die GitHub Action erledigt genau das. Mit --format=json steht vor jeder Datei eine Zeile FILE: … mit einem eigenen JSON-Array, die Ausgabe als Ganzes ist also kein gültiges JSON; --format=strict-json liefert ein einziges Array, allerdings ohne Dateinamen. Ohne Befunde geben beide Formate nur eine Erfolgszeile aus.
Ein Beispiel-Plugin mit eingebauten Fehlern
Das Beispiel besteht aus einer Datei im Ordner wp-content/plugins/lw-hello-banner/, der Slug lautet damit lw-hello-banner. Das Plugin zeigt oben auf der Seite einen Gruß, liest einen optionalen Namen aus der URL, bietet unter Einstellungen → Allgemein ein Notizfeld und lädt ein dekoratives Skript. Neun Fehler sind absichtlich enthalten: keine readme.txt, kein Lizenz-Header, eine vom Slug abweichende Text Domain, kein Schutz gegen direkten Dateiaufruf, ein Aufruf von error_reporting(), register_setting() ohne Bereinigung, Ausgaben ohne Escaping, ein übersetzbarer String mit Platzhalter, aber ohne Übersetzerkommentar, und ein Skript vom CDN ohne Version und Ladeargumente.
<?php
/**
* Plugin Name: LW Hello Banner
* Description: Shows a greeting banner at the top of the front page.
* Version: 0.1.0
* Author: Lukas Wojcik
* Text Domain: hello-banner
*/
// Intentionally faulty example for Plugin Check. Do not use on a live site.
error_reporting( E_ALL );
function lw_hello_banner_register_setting() {
register_setting( 'general', 'lw_hello_banner_note' );
add_settings_field(
'lw_hello_banner_note',
__( 'Banner note', 'hello-banner' ),
'lw_hello_banner_field',
'general'
);
}
add_action( 'admin_init', 'lw_hello_banner_register_setting' );
function lw_hello_banner_field() {
echo '<input type="text" name="lw_hello_banner_note" value="' . get_option( 'lw_hello_banner_note' ) . '">';
}
function lw_hello_banner_assets() {
wp_enqueue_script( 'lw-hello-banner', 'https://cdn.jsdelivr.net/npm/canvas-confetti@1.9.3/dist/confetti.browser.min.js' );
}
add_action( 'wp_enqueue_scripts', 'lw_hello_banner_assets' );
function lw_hello_banner_render() {
$name = isset( $_GET['lw_name'] ) ? $_GET['lw_name'] : 'Guest';
$note = get_option( 'lw_hello_banner_note', '' );
echo '<div class="lw-hello-banner"><p>' . sprintf( __( 'Hello, %s!', 'hello-banner' ), $name ) . ' ' . $note . '</p></div>';
}
add_action( 'wp_body_open', 'lw_hello_banner_render' );
Was Plugin Check dazu meldet
Die Tabelle führt die Befunde auf, die wp plugin check lw-hello-banner mit Plugin Check 2.1.0 unter WordPress 7.1.2 meldet, zusammen zwölf Fehler und neun Warnungen; ein Lauf mit --require liefert dieselbe Liste. Zeilennummern fehlen; OutputNotEscaped erscheint viermal, TextDomainMismatch und NonceVerification.Recommended je zweimal.
| Ergebniscode | Prüfung (Kategorie) | Typ | Ursache im Beispiel |
|---|---|---|---|
no_plugin_readme |
plugin_readme (plugin_repo) |
ERROR | keine readme.txt |
plugin_header_no_license |
plugin_header_fields (plugin_repo) |
ERROR | kein License-Header |
textdomain_mismatch |
plugin_header_fields (plugin_repo) |
WARNING | Header nennt hello-banner, Slug ist lw-hello-banner |
missing_direct_file_access_protection |
direct_file_access (security, plugin_repo) |
ERROR | Funktionen und Hooks ohne ABSPATH-Abfrage |
WordPress.WP.I18n.TextDomainMismatch |
i18n_usage (general, plugin_repo) |
ERROR | __()-Aufrufe mit falscher Text Domain |
WordPress.WP.I18n.MissingTranslatorsComment |
i18n_usage (general, plugin_repo) |
ERROR | %s ohne translators:-Kommentar |
PluginCheck.CodeAnalysis.PHPErrorReporting.DirectErrorReportingCall |
php_error_reporting (general) |
WARNING | error_reporting( E_ALL ) |
WordPress.PHP.DevelopmentFunctions.prevent_path_disclosure_error_reporting |
plugin_review_phpcs (plugin_repo) |
WARNING | error_reporting( E_ALL ) |
PluginCheck.CodeAnalysis.SettingSanitization.register_settingMissing |
setting_sanitization (plugin_repo) |
ERROR | register_setting() ohne drittes Argument |
WordPress.Security.EscapeOutput.OutputNotEscaped |
late_escaping (security, plugin_repo) |
ERROR | get_option(), __(), $name und $note im echo |
PluginCheck.CodeAnalysis.EnqueuedResourceOffloading.OffloadedContent |
offloading_files (plugin_repo) |
ERROR | Skript-URL auf cdn.jsdelivr.net |
WordPress.WP.EnqueuedResourceParameters.MissingVersion, .NotInFooter |
enqueued_scripts_in_footer (performance) |
WARNING | keine Version, kein fünftes Argument |
WordPress.Security.NonceVerification.Recommended |
plugin_review_phpcs (plugin_repo) |
WARNING | $_GET['lw_name'] ohne Nonce-Prüfung |
WordPress.Security.ValidatedSanitizedInput.MissingUnslash, .InputNotSanitized |
plugin_review_phpcs (plugin_repo) |
WARNING | $_GET['lw_name'] unbehandelt verwendet |

Drei Dinge fallen auf. Erstens meldet Plugin Check die Text Domain doppelt, aus zwei verschiedenen Prüfungen: Die Header-Prüfung vergleicht den Header Text Domain mit dem Slug und gibt eine Warnung aus, der i18n-Sniff vergleicht jeden Übersetzungsaufruf mit dem Slug und gibt einen Fehler aus. Zweitens stammen die Befunde zu Nonce und Bereinigung nicht aus der Kategorie Security, sondern aus plugin_review_phpcs, einem PHPCS-Lauf mit dem Review-Regelwerk von Plugin Check; ein Lauf mit --categories=security zeigt sie nicht. Drittens enthält das Regelwerk auch die Sniff-Gruppe WordPress.PHP.DevelopmentFunctions als Warnung. Sie meldet die Zeile mit error_reporting() tatsächlich ein zweites Mal, als prevent_path_disclosure_error_reporting.
Beim CDN-Skript kommt ein weiterer Punkt hinzu: Die Laufzeitprüfungen zu Reichweite und Ladestrategie von Skripten werten nur Skripte aus, deren URL im Plugin-Ordner liegt. Das CDN-Skript melden deshalb nur die statischen Prüfungen, und der Lauf mit --require liefert dieselbe Liste wie der ohne. Zum Tragen kommen die Laufzeitprüfungen erst bei der korrigierten Fassung mit mitgeliefertem Skript: Ohne die Bedingung is_front_page() meldet Plugin Check es als EnqueuedScriptsScope („This script is being loaded in all frontend contexts.“), aber nur in einem Lauf mit --require.
Die korrigierte Fassung
Das korrigierte Plugin besteht aus drei Dateien: der Hauptdatei, einer readme.txt und einem kleinen lokalen Skript in assets/. Die Hauptdatei erhält einen vollständigen Header mit Lizenz, Requires at least und der Text Domain lw-hello-banner sowie eine ABSPATH-Abfrage. Der Aufruf von error_reporting() entfällt. register_setting() bekommt Typ, sanitize_callback und Standardwert. Ausgaben werden erst beim Ausgeben mit esc_attr(), esc_html() und esc_html__() geschützt. Das Skript liegt im Plugin, trägt eine Version und wird nur auf der Startseite mit in_footer und strategy geladen. Die Array-Form des fünften Arguments von wp_enqueue_script() gibt es seit WordPress 6.3, was zur angegebenen Mindestversion passt.
<?php
/**
* Plugin Name: LW Hello Banner
* Description: Shows a greeting banner at the top of the front page.
* Version: 0.2.0
* Requires at least: 6.3
* Requires PHP: 7.4
* Author: Lukas Wojcik
* License: GPLv2 or later
* License URI: https://www.gnu.org/licenses/gpl-2.0.html
* Text Domain: lw-hello-banner
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
define( 'LW_HELLO_BANNER_VERSION', '0.2.0' );
/**
* Registers the banner note setting and its field on Settings > General.
*/
function lw_hello_banner_register_setting() {
register_setting(
'general',
'lw_hello_banner_note',
array(
'type' => 'string',
'sanitize_callback' => 'sanitize_text_field',
'default' => '',
)
);
add_settings_field(
'lw_hello_banner_note',
__( 'Banner note', 'lw-hello-banner' ),
'lw_hello_banner_field',
'general',
'default',
array( 'label_for' => 'lw_hello_banner_note' )
);
}
add_action( 'admin_init', 'lw_hello_banner_register_setting' );
/**
* Prints the input field for the banner note.
*/
function lw_hello_banner_field() {
printf(
'<input type="text" class="regular-text" id="lw_hello_banner_note" name="lw_hello_banner_note" value="%s">',
esc_attr( get_option( 'lw_hello_banner_note', '' ) )
);
}
/**
* Loads the bundled script on the front page only.
*/
function lw_hello_banner_assets() {
if ( ! is_front_page() ) {
return;
}
wp_enqueue_script(
'lw-hello-banner',
plugins_url( 'assets/lw-hello-banner.js', __FILE__ ),
array(),
LW_HELLO_BANNER_VERSION,
array(
'in_footer' => true,
'strategy' => 'defer',
)
);
}
add_action( 'wp_enqueue_scripts', 'lw_hello_banner_assets' );
/**
* Prints the banner on the front page.
*/
function lw_hello_banner_render() {
if ( ! is_front_page() ) {
return;
}
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only display value, nothing is stored.
$name = isset( $_GET['lw_name'] ) ? sanitize_text_field( wp_unslash( $_GET['lw_name'] ) ) : '';
if ( '' === $name ) {
$name = __( 'Guest', 'lw-hello-banner' );
}
$greeting = sprintf(
/* translators: %s: visitor name */
__( 'Hello, %s!', 'lw-hello-banner' ),
$name
);
$note = get_option( 'lw_hello_banner_note', '' );
echo '<div class="lw-hello-banner"><p>' . esc_html( $greeting );
if ( '' !== $note ) {
echo ' ' . esc_html( $note );
}
echo '</p><button type="button">' . esc_html__( 'Dismiss', 'lw-hello-banner' ) . '</button></div>';
}
add_action( 'wp_body_open', 'lw_hello_banner_render' );
Den Namen aus der URL behält die Fassung bewusst bei, und zwar im Parameter lw_name: WordPress selbst liest name als Beitrags-Slug, ?name=Anna macht aus der Startseite deshalb eine 404-Seite, auf der is_front_page() false liefert. Er wird mit wp_unslash() und sanitize_text_field() behandelt, die verbleibende Nonce-Warnung unterdrückt ein phpcs:ignore-Kommentar, der den Sniff nennt und eine Begründung trägt. Die FAQ von Plugin Check beschreibt solche Annotationen als vorgesehenen Weg für Fehlalarme von PHPCS in einer bestimmten Zeile. Die Begründung trägt hier nur, weil der Wert nichts verändert: Er wird nicht gespeichert und löst keine Aktion aus. Bei einem Formular, das Daten speichert, gehören Nonce und Capability-Prüfung dazu; das behandelt ein eigener Beitrag dieser Reihe ausführlich.
=== LW Hello Banner ===
Contributors: lukaswojcik
Tags: banner, greeting
Requires at least: 6.3
Tested up to: 7.1
Requires PHP: 7.4
Stable tag: 0.2.0
License: GPLv2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html
Shows a short, dismissible greeting banner at the top of the front page.
== Description ==
LW Hello Banner prints a greeting on the front page. An optional note can be set under Settings > General.
== Changelog ==
= 0.2.0 =
* Fix all findings reported by Plugin Check.
= 0.1.0 =
* Initial version.
Zwei Details der Readme ergeben sich direkt aus dem Code der Prüfung. „Tested up to“ muss der aktuellen Hauptversion von WordPress entsprechen, derzeit 7.1. Ein älterer Wert ist bei neuen Plugins ein Fehler und im Update-Modus eine Warnung; ein Wert mit Patch-Stufe wie 7.1.2 wird als invalid_tested_upto_minor gemeldet. Außerdem vergleicht die Prüfung die Lizenz in der Readme mit der im Plugin-Header und meldet Abweichungen als license_mismatch, deshalb steht in beiden Dateien derselbe Wortlaut. Die Felder von Readme und Plugin-Header behandelt ein eigener Beitrag dieser Reihe.
// assets/lw-hello-banner.js
( function () {
const banner = document.querySelector( '.lw-hello-banner' );
if ( ! banner ) {
return;
}
const button = banner.querySelector( 'button' );
if ( button ) {
button.addEventListener( 'click', function () {
banner.hidden = true;
} );
}
} )();
Plugin Check in GitHub Actions
Die offizielle Anbindung ist das Repository WordPress/plugin-check-action. Die neueste Version ist v1.1.9 vom 11. August 2026, das mitwandernde Tag v1 zeigt derzeit auf denselben Commit. Es handelt sich um eine Composite Action: Zuerst richtet sie Node 24 ein, installiert @wordpress/env, startet eine WordPress-Instanz mit dem Plugin-Ordner unter wp-content/plugins, installiert Plugin Check aus dem Verzeichnis, dazu die unter Requires Plugins genannten Plugins, und ruft dann wp plugin check mit --format=json und --require=./wp-content/plugins/plugin-check/cli.php auf. Laufzeitprüfungen sind also dabei. Laut Dokumentation läuft wp-env standardmäßig auf Docker, der übliche Ubuntu-Runner ist deshalb die naheliegende Wahl.
Ein Node-Skript wertet die JSON-Datei aus. Jedes Ergebnis wird zu einer Annotation an der betroffenen Datei, mit dem Ergebniscode als Titel. Jedes ERROR setzt den Exitcode auf 1 und lässt den Job scheitern, Warnungen erscheinen nur als Annotation. Mit strict: true zählt jedes Ergebnis als Fehler. Bei Pull Requests schreibt oder aktualisiert die Action zusätzlich einen Zusammenfassungskommentar, das Rohergebnis landet als Artefakt plugin-check-results.
Eingaben in v1.1.9: build-dir (Standard ./), checks, exclude-checks, categories, exclude-files, exclude-directories, ignore-codes, ignore-warnings, ignore-errors, include-experimental, wp-version (latest oder trunk), severity, error-severity, warning-severity, include-low-severity-errors, include-low-severity-warnings, slug, strict und repo-token. Listen-Eingaben nehmen einen Eintrag je Zeile.
Zwei Details entscheiden darüber, ob die Ergebnisse stimmen. Die Action leitet den Slug aus dem letzten Teil von build-dir ab. Beim Standard ./ ist das der Name des Checkout-Verzeichnisses, und der entspricht laut GitHub-Dokumentation dem Repository-Namen, etwa /home/runner/work/my-repo-name/my-repo-name. Heißt das Repository anders als das Plugin, fällt jeder Übersetzungsaufruf durch. Zudem warnt die Prüfung file_type in 2.1.0 vor einem Ordner .github im Plugin und beanstandet weitere versteckte Dateien. Wer das Repository-Wurzelverzeichnis prüft, prüft also Dateien, die nie ausgeliefert werden. Der folgende Workflow baut deshalb mit git archive den Ordner, der tatsächlich hochgeladen würde, berücksichtigt dabei die export-ignore-Einträge aus .gitattributes und übergibt diesen Ordner als build-dir.
# .github/workflows/plugin-check.yml
name: Plugin Check
on:
pull_request:
push:
branches:
- main
permissions:
contents: read
pull-requests: write # only for the summary comment on pull requests
jobs:
plugin-check:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v7
# Build the folder that would be shipped, without .github and other
# development files (see export-ignore in .gitattributes).
- name: Build distributable plugin folder
run: |
mkdir -p build
git archive --format=tar --prefix=lw-hello-banner/ HEAD | tar -x -C build
- name: Run Plugin Check
uses: wordpress/plugin-check-action@v1
with:
# The folder name becomes the slug (text domain check, readme check).
build-dir: ./build/lw-hello-banner
wp-version: latest
# Optional filters, one entry per line:
# categories: |
# plugin_repo
# security
# exclude-checks: |
# enqueued_scripts_scope
# ignore-codes: |
# textdomain_mismatch
# Fail on warnings as well:
# strict: true
# .gitattributes: files that git archive leaves out of the build
/.gitattributes export-ignore
/.github export-ignore
/.gitignore export-ignore
/build export-ignore
Die Berechtigung pull-requests: write ist nur für den Zusammenfassungskommentar nötig; laut den Berechtigungstabellen von GitHub deckt sie den Kommentar-Endpunkt ab. Fehlt sie, protokolliert die Action eine Warnung beim Kommentieren, wertet die Ergebnisse aber trotzdem aus. Wer feste Versionen bevorzugt, verweist statt auf v1 auf wordpress/plugin-check-action@v1.1.9 oder auf dessen Commit-Hash.
Grenzen und offene Punkte
- Plugin Check ersetzt das manuelle Review nicht. Die Plugin-Seite sagt das ausdrücklich, und ein sauberer Lauf garantiert keine Freigabe.
- Die Befundtabelle zum Beispiel-Plugin stammt aus einem lokalen Lauf von
wp plugin checkunter WordPress 7.1.2. Auf GitHub lief der Workflow nicht; lokal erzeugte der Schritt mitgit archiveden erwarteten Ordner, und das Auswerteskript der Action (v1.1.9) endete beim fehlerhaften Beispiel mit Exitcode 1, bei der korrigierten Fassung mit 0. - Manche Prüfungen arbeiten mit Heuristiken. Die Präfix-Prüfung leitet die erwarteten Präfixe aus dem Plugin-Code ab, deshalb zählt ein durchgängiges Präfix wie
lw_hello_banner_. - Die Action installiert die Plugin-Check-Version, die das Verzeichnis zum Zeitpunkt des Laufs anbietet, und hat keine Eingabe, um sie festzuschreiben. Eine Pipeline kann also nach einem Plugin-Check-Release rot werden, ohne dass sich am Plugin etwas geändert hat.
- Für die PHP-Version gibt es keine Eingabe, und
wp-versionunterscheidet nur zwischen aktueller Version und trunk. Tests gegen ältere WordPress- oder PHP-Versionen brauchen einen eigenen Aufbau, etwa mit wp-env oder Playground; darum geht es in einem eigenen Beitrag dieser Reihe. - Performance-Befunde zur Reichweite von Skripten hängen davon ab, wie das Plugin Dateien einbindet; die andere Seite des Problems zeigt der Beitrag zum gezielten Dequeuing von Plugin-Skripten.
Fragen und Antworten
Garantiert ein Lauf ohne Befunde, dass das Plugin ins Verzeichnis aufgenommen wird?
Nein. Laut FAQ von Plugin Check muss ein Plugin in der Regel alle Prüfungen der Kategorie plugin_repo bestehen, die Freigabe hängt aber weiter vom manuellen Review ab. Die Plugin-Seite hält ausdrücklich fest, dass Plugin Check das manuelle Review nicht ersetzt; ein sauberer Lauf beschleunigt es nur.
Warum meldet die GitHub Action mehr als der lokale Aufruf von wp plugin check?
Dafür gibt es drei typische Gründe:
- Die Action lädt immer
cli.phpper--requireund aktiviert das Plugin, lokal laufen ohne diesen Umweg oder bei inaktivem Plugin nur statische Prüfungen. - Die Action installiert die jeweils aktuelle Plugin-Check-Version aus dem Verzeichnis, lokal kann eine ältere aktiv sein.
- Mit
build-dir: ./prüft sie das ganze Repository samt.githubund versteckten Dateien.
Wann ist es vertretbar, eine Warnung zu unterdrücken?
Wenn der Befund im konkreten Fall nachweislich kein Problem ist, etwa ein nur angezeigter, bereinigter URL-Parameter ohne Speichervorgang. Dann gehört ein phpcs:ignore-Kommentar mit Sniff-Name und Begründung direkt an die Zeile. --ignore-codes oder die Action-Eingabe ignore-codes blenden einen Code dagegen im ganzen Plugin aus und verdecken damit auch echte Treffer.
Warum wird dieselbe Text Domain zweimal beanstandet?
Zwei Prüfungen vergleichen mit dem Slug: plugin_header_fields prüft den Header Text Domain und meldet textdomain_mismatch als Warnung, i18n_usage prüft jeden Übersetzungsaufruf und meldet WordPress.WP.I18n.TextDomainMismatch. Der Slug ist der Ordnername; in der CI lässt er sich mit der Eingabe slug oder lokal mit --slug festlegen.
Quellen
- Plugin Check (PCP) auf WordPress.org, abgerufen am 29.09.2026
- WordPress/plugin-check, README, abgerufen am 29.09.2026
- Plugin Check Release 2.1.0, abgerufen am 29.09.2026
- Plugin Check: WP-CLI-Dokumentation (docs/CLI.md), abgerufen am 29.09.2026
- Plugin Check: verfügbare Prüfungen (docs/checks.md), abgerufen am 29.09.2026
- Default_Check_Repository.php (Tag 2.1.0), abgerufen am 29.09.2026
- Check_Categories.php (Tag 2.1.0), abgerufen am 29.09.2026
- Plugin_Check_Command.php (Tag 2.1.0), abgerufen am 29.09.2026
- plugin-check.ruleset.xml (Tag 2.1.0), abgerufen am 29.09.2026
- Quelltext der Prüfungen (Tag 2.1.0), abgerufen am 29.09.2026
- Unit-Tests der Prüfungen (Tag 2.1.0), abgerufen am 29.09.2026
- WordPress/plugin-check-action, README, abgerufen am 29.09.2026
- plugin-check-action: action.yml (v1.1.9), abgerufen am 29.09.2026
- plugin-check-action: src/main.ts (v1.1.9), abgerufen am 29.09.2026
- plugin-check-action Release v1.1.9, abgerufen am 29.09.2026
- wp_enqueue_script() – Developer Resources, abgerufen am 29.09.2026
- @wordpress/env – Block Editor Handbook, abgerufen am 29.09.2026
- WordPress.org Versions-API (aktuelle Version 7.1.2), abgerufen am 29.09.2026
- actions/checkout Releases (v7.0.1), abgerufen am 29.09.2026
- GitHub Docs: Variablen (GITHUB_WORKSPACE), abgerufen am 29.09.2026
- GitHub Docs: Berechtigungen für GitHub Apps, abgerufen am 29.09.2026