# WordPress.org Plugin Directory Guidelines — Review Checklist Source: [Detailed Plugin Guidelines](https://developer.wordpress.org/plugins/wordpress-org/detailed-plugin-guidelines/?output_format=md) Use this section as a structured checklist when reviewing a plugin. Each guideline includes the violation signal to look for, the verdict to issue, and the fix to recommend. Cite the guideline number in every finding. ## Contents - [WordPress.org Plugin Directory Guidelines — Review Checklist](#wordpressorg-plugin-directory-guidelines--review-checklist) - [Contents](#contents) - [Guideline 1: GPL-Compatible License](#guideline-1-gpl-compatible-license) - [Guideline 2: Developer Responsibility](#guideline-2-developer-responsibility) - [Guideline 3: Stable Version in SVN](#guideline-3-stable-version-in-svn) - [Guideline 4: Human-Readable Code](#guideline-4-human-readable-code) - [Guideline 5: No Trialware](#guideline-5-no-trialware) - [Guideline 6: SaaS Integrations Are Allowed — With Conditions](#guideline-6-saas-integrations-are-allowed--with-conditions) - [Guideline 7: No External Data Collection Without Consent](#guideline-7-no-external-data-collection-without-consent) - [Guideline 8: No Remotely Loaded Executable Code](#guideline-8-no-remotely-loaded-executable-code) - [Guideline 9: No Illegal, Dishonest, or Offensive Behavior](#guideline-9-no-illegal-dishonest-or-offensive-behavior) - [Guideline 10: No Forced External Links](#guideline-10-no-forced-external-links) - [Guideline 11: No Admin Dashboard Hijacking](#guideline-11-no-admin-dashboard-hijacking) - [Guideline 12: No Readme Spam](#guideline-12-no-readme-spam) - [Guideline 13: Use WordPress-Bundled Libraries](#guideline-13-use-wordpress-bundled-libraries) - [Guideline 14: SVN Is a Release Repository](#guideline-14-svn-is-a-release-repository) - [Guideline 15: Increment Version Numbers](#guideline-15-increment-version-numbers) - [Guideline 16: Plugin Must Be Complete at Submission](#guideline-16-plugin-must-be-complete-at-submission) - [Guideline 17: Respect Trademarks and Copyrights](#guideline-17-respect-trademarks-and-copyrights) - [Guideline 18: WordPress.org Reserves Directory Rights](#guideline-18-wordpressorg-reserves-directory-rights) --- ### Guideline 1: GPL-Compatible License **Check:** Does the main plugin file have a `License:` header with a GPL-compatible value? Are all bundled third-party libraries under compatible licenses? **Violation signals:** - Missing `License:` or `License URI:` header in the main plugin file - License is `Proprietary`, `All Rights Reserved`, `CC-BY-NC`, `CC-BY-ND`, `SSPL`, `BSL`, `Commons Clause`, `EPL`, `EUPL`, or `MPL-1.0` - Bundled library under a license not in the GPL-Compatible Licenses table (see below) - PHP files encoded with ionCube, Zend Guard, or similar — source cannot be exercised → violation **Verdict:** Flag as **FAIL** with the specific file and license value found. **Fix:** Use `GPL-2.0-or-later` (recommended). Add full license text or a `License URI:` to `https://www.gnu.org/licenses/gpl-2.0.html`. Replace incompatible libraries. --- ### Guideline 2: Developer Responsibility **Check:** Has the developer deliberately re-introduced previously removed code, circumvented a prior guideline decision, or included files they cannot legally distribute? **Violation signals:** - Commit history shows restoring a file after it was removed by the review team - Bundled assets with no documented license (treat as unlicensed until proven otherwise) - Third-party API terms prohibit redistribution of the bundled SDK **Verdict:** Flag as **FAIL**. Document the specific file or commit. **Fix:** Remove the offending file or obtain and document proper licensing. --- ### Guideline 3: Stable Version in SVN **Check:** Is the WordPress.org SVN version the canonical release? Is the plugin also distributed via an external channel with a newer version? **Violation signals:** - `readme.txt` advertises a version not present in SVN trunk/tags - External download page (developer's own site) offers a newer build than WP.org - Plugin auto-updates itself from a non-WP.org server (also a Guideline 8 issue) **Verdict:** Flag as **FAIL** if an actively maintained external version is ahead of the directory. **Fix:** Keep SVN up to date. External channels may mirror but must not supersede the directory version. --- ### Guideline 4: Human-Readable Code **Check:** Is all PHP, JS, and CSS in a form that a developer can read and understand? Are build sources available? **Violation signals:** - PHP obfuscated with packer, eval+base64 chains, or variable names like `$a1b2c3` throughout - Minified JS present **without** any source map or reference to the source repo/file in the readme - Build artifacts (`.min.js`) committed with no corresponding unminified source in the package or a public repo linked from `readme.txt` **Verdict:** Flag as **FAIL** for obfuscated PHP (always). Flag minified-only JS as **FAIL** if no source access is documented. **Fix:** Remove obfuscation. Add a `Development` or `Build` section to `readme.txt` linking to the source repo (GitHub, GitLab, etc.). --- ### Guideline 5: No Trialware **Core rule:** Every feature shipped in the directory must function end-to-end without a license key, payment, or account. **Check for each feature gate in the code:** 1. Does a `has_paid_access()` / `is_licensed()` / `check_license()` check gate **local** processing (not an external service call)? 2. Is there a time-based expiry (`time() > $installed_at + 30 * DAY_IN_SECONDS`) for local behavior? 3. Is there a usage quota (`if ( $count >= 100 )`) that is artificially low and only exists to pressure upgrades? 4. Does the free user see a blocked/locked UI that prevents completing a core workflow? **Violation signals (flag as FAIL):** - `return` / `wp_die()` / blocking screen shown when `has_paid_access()` is false for a local feature - Ternary limits: `$limit = $licensed ? 10000 : 100` with no filter to extend the free cap - Features expire after X days even when no external service is involved - Admin screen is entirely replaced with an upgrade prompt **Allowed patterns (do not flag):** - Upsell notice shown alongside a working free feature (non-blocking) - Premium feature delegated to a **separate** add-on plugin not hosted on WP.org - External SaaS feature gated because the **service** itself requires payment (e.g., AI API quota) - Dismissible comparison table or upgrade button in plugin settings **Code patterns:** ```php // VIOLATION — local feature blocked by paid check if ( ! $this->has_paid_access() ) { echo 'Upgrade required'; return; // ← blocks execution } // VIOLATION — artificial cap with no extension point $limit = $this->has_paid_access() ? 10000 : 100; ``` ```php // COMPLIANT — free path works; premium adds to it $this->render_basic_export(); if ( $this->has_premium_addon() ) { do_action( 'myplugin_premium_export_options' ); } // COMPLIANT — cap is consistent; extensible via filter $limit = apply_filters( 'myplugin_event_limit', 10000 ); ``` **Pre-submission checklist:** - [ ] All free features work without a license key - [ ] No time-based expirations or usage quotas for local behavior - [ ] No blocking/locked UI preventing free-tier workflows - [ ] Upsell prompts are informational, non-blocking, and dismissible - [ ] Premium-only code lives in a separate add-on or an external service --- ### Guideline 6: SaaS Integrations Are Allowed — With Conditions **Check:** Does the external service provide real functionality? Is it documented in the readme? **Violation signals:** - The external service's sole purpose is validating a license key; all actual processing is local - Code was moved server-side specifically to disguise what is really a local feature gate - Plugin is a storefront or checkout page for an external product with no real plugin functionality **Verdict:** Flag as **FAIL** for license-validation-only services. Do not flag genuine SaaS integrations. **Fix:** Document what the external service does in `readme.txt`. Move license validation out of the plugin's critical path if the functionality is local. --- ### Guideline 7: No External Data Collection Without Consent **Check:** Does the plugin send any data to an external server without the user explicitly opting in? **Violation signals:** - HTTP request to a remote URL on plugin activation, admin page load, or cron job with no user opt-in - User email, site URL, or usage data sent without a visible opt-in checkbox or registration step - Third-party analytics or ad-tracking scripts loaded in admin or frontend without consent - Assets (images, fonts, scripts) loaded from an external CDN that are not the plugin's primary service **Exception:** Plugins that are interfaces to a named third-party service (e.g., Akismet, Mailchimp, a CDN) — consent is implied when the user configures the service connection. **Code patterns (violation vs compliant):** ```php // VIOLATION — sends data on activation without consent register_activation_hook( __FILE__, function() { wp_remote_post( 'https://api.example.com/collect', array( 'body' => array( 'site' => home_url(), 'admin_email' => get_option( 'admin_email' ), ), ) ); } ); // COMPLIANT — explicit opt-in gate if ( isset( $_POST['myplugin_opt_in'] ) && '1' === $_POST['myplugin_opt_in'] ) { update_option( 'myplugin_tracking_opt_in', 1 ); } if ( get_option( 'myplugin_tracking_opt_in' ) ) { wp_remote_post( 'https://api.example.com/collect', $payload ); } ``` **Review questions:** 1. Is any outbound request made on activation, first-run, or cron before consent is stored? 2. Is the opt-in UI explicit, unambiguous, and default-off? 3. Is consent persisted and checked before every telemetry request path? 4. Are collected fields documented in `readme.txt` privacy disclosures? **Verdict:** Flag as **FAIL** for any unconsented outbound call. Include the specific URL or domain found. **Fix:** Wrap all outbound calls in an opt-in gate. Add a `Privacy Policy` section to `readme.txt` describing what data is collected and why. **Pre-submission checklist:** - [ ] No telemetry/analytics calls occur before explicit opt-in - [ ] Opt-in control is visible and off by default - [ ] Consent is stored and checked in every outbound path - [ ] Privacy policy in readme explains data, destination, and purpose --- ### Guideline 8: No Remotely Loaded Executable Code **Check:** Is all JS/CSS that runs on the user's site included in the plugin package? **Violation signals:** - `wp_enqueue_script()` loading JS from a third-party CDN (not a self-hosted asset) - Plugin fetches and executes code from an external URL at runtime (`file_get_contents` + `eval`, dynamic `