Skip to content
System nominalUTC --:--:--Articles 080
MODULE 06 Lesson 17 / 18 35 MIN

Shipping

The WordPress.org Submission Checklist

The rules the plugin review team actually enforces, the licence and disclosure boxes you have to tick, and the SVN repository you get when the plugin is approved.

You have a working plugin, a readme, and a zip. You submit it, and three weeks later the review email lists four things: the bundled charting library is MIT but nothing in the plugin says so; the settings screen contacts an API on save without a word about it in the readme; Requires at least claims 6.0 while the plugin calls a function added in 6.5; and the slug begins with another product's name. None of those are bugs. Every one is a rejection, and each fix is a line of text.

The review team publishes the rules it enforces as the Detailed Plugin Guidelines, eighteen numbered items, and the ones people trip over are a small subset. This lesson goes through that subset in the order a reviewer meets it, then the mechanics of submission.

Guideline 1 is a licence, and the header must say which one

The first guideline requires GPL compatibility for the plugin and everything shipped inside it: all code, data and images. Using the same licence as WordPress — GPLv2 or later — is what the handbook recommends, and any licence on GNU's GPL-compatible list is acceptable. The header carries it:

/**
 * Plugin Name: WPSifero Notes
 * Version:     2.0.0
 * License:     GPL-2.0-or-later
 * License URI: https://www.gnu.org/licenses/gpl-2.0.html
 */

Note what that header does not do. get_plugin_data() never reads License or License URI, and WordPress never checks them; they are for the reviewer, the directory page, and whoever forks your plugin later. The readme carries the same pair.

The second guideline extends the licence question to everything you bundled, and that is the part people miss: a library feels like a dependency rather than code you are redistributing. If you ship a minified charting library you are distributing that library, and the directory needs to see that it is compatible. Vendored libraries get a licenses.txt or a comment block naming the library, its version, its licence and where it came from. Guideline 2 says a library whose licensing cannot be validated cannot be used at all.

Guideline 13 adds one more: you may not bundle a copy of a library WordPress already ships. jQuery, PHPMailer and SimplePie are in core, and a plugin loading its own version creates two copies on the page. Use wp_enqueue_script() with core's handle.

Two guidelines govern what the plugin does after installation, and they cause the most arguments. The first is guideline 7: a plugin may not contact external servers without explicit, authorised consent. The acceptable shapes of consent are an opt-in registration with your service or a checkbox in the settings. The prohibited ones include automated collection of user data, assets loaded from a third-party host, and undocumented use of external data such as a remote blocklist. What is collected and how it is used belongs in the readme. A plugin that is genuinely a front end for a service has consent implied by installing, activating and registering for it; that is the exception guideline 6, Software as a Service, covers. A version check against your own update server is the pattern reviewers look at hardest, and doing it while the readme says nothing is the shape of a rejection.

Trialware, serviceware and the admin notices in between

Guideline 5 bans functionality that is restricted or locked and released only by payment or upgrade, and its examples close the obvious loopholes. Disabling a feature after a trial period or a usage quota counts. Sandbox-only API access counts, because the useful version of the service is behind a payment. What does not count is a plugin whose code is fully present but which talks to a paid service: that is serviceware, permitted when the service provides real functionality and is documented in the readme. Add-on plugins hosted outside the directory are the handbook's own remedy for keeping premium code out of the free package.

Guideline 11 limits advertising inside the admin: prompts must be sparing, contextual or confined to the plugin's settings page, and a site-wide notice must be dismissible or self-dismissing. Guideline 10 bans credit links and "powered by" output on the front end unless the user opted in, and requires them to default to off. If you planned a footer credit in exchange for a licence key, re-read that one.

Requires at least and Tested up to are different fields read by different systems

These two look symmetrical and are not. Getting them wrong produces the most common directory-page complaint.

Requires at least is the minimum WordPress version the plugin works on, read from the plugin header by get_plugin_data(), which returns it as RequiresWP. It powers the check that runs before activation and an update. A plugin declaring Requires at least: 7.0 is refused on 6.9 when it arrives as an upload or an update, with the header value quoted in the error, and when the readme disagrees the header decides. The readme line is for the directory page; putting it only there leaves WordPress nothing to check.

Tested up to has no header at all. The directory reads it from the readme and returns it in the plugin API response, which is why the FAQ answers "why does my plugin say it's not been tested with the most recent WordPress versions" with an instruction to edit the readme. It ignores minor versions, so 7.0 covers every 7.0.x, and it must not name a version that does not exist yet: the FAQ allows a release candidate at most. When a new major lands, edit the readme value and push a new tag, because a confirmed release is frozen.

The "Out of Date" notice is about the last three majors

A plugin that does not declare support for the last three major WordPress releases gets a notice on its directory page. The fix is a readme edit, not a code change, which is why the review team emails plugin authors before every major release asking them to bump Tested up to. A plugin that has not been touched in two years and one that has not been tested in two years look the same to a visitor, and only one of them is a real problem.

The slug is permanent and may not begin with somebody else's name

Guideline 17 is the one that ends submissions early. Using a trademark or another project's name as the sole or initial term of a plugin slug is prohibited unless you can prove legal ownership. An unaffiliated plugin may call itself "Dancing Sloths for Superbox" but not "Super Sandbox Dancing Sloths", because the second implies a relationship that does not exist. A company cannot grant permission either, because the directory will not accept permission as a defence.

Your slug comes from the Plugin Name in the main plugin file, and the submission email tells you what it will be. You can change it once; after that it takes an email to the plugins team. The other rules: no vulgarities, no wordpress or plugin except in extraordinary cases, no version numbers, and English letters and Arabic numerals only. Display names come from the same header, so a rename means editing the header and the readme together.

The readme has its own spam rule, guideline 12, which is where the tags limit comes from: five tags maximum, only the first five count, no competitor's plugin name, no keyword stuffing. A tag only your plugin uses is not displayed at all.

Submission is a zip, a queue, and then an SVN repository

The mechanics are simpler than the rules. Register on WordPress.org with an email address you actually read, whitelist plugins@wordpress.org so review mail does not land in spam, and submit a complete zip through the Add page. The zip must be under 10 MB, installable through the Upload Plugin screen, and free of test folders, node_modules, ungenerated source and development tooling. An oversized submission is almost always a packaging mistake.

After that it is a queue: the FAQ puts a small, correct plugin at a soft fourteen days, longer if there is anything to fix. Issues in the review are fixed and answered, not a permanent rejection. Guideline 16 exists because of incomplete submissions: names cannot be reserved, and unused directory names may be handed to another developer.

Approval gives you an SVN repository at https://plugins.svn.wordpress.org/<slug> with three directories. /trunk/ holds the plugin code including readme.txt and the main plugin file at its top level — not in a subdirectory, because that breaks the zip generator. /tags/ holds one folder per release named after the version number. /assets/ holds screenshots, banners and icons as a sibling of trunk, and /branches/ is no longer created.

svn co https://plugins.svn.wordpress.org/wpsifero-notes wpsifero-notes-svn
cd wpsifero-notes-svn
# Edit files inside trunk/, then:
svn add trunk/*
svn ci -m "Adding first version of the plugin"
# Tag a release from trunk rather than copying files into the tag by hand:
svn cp trunk tags/1.0.0
svn ci -m "Tagging version 1.0.0"

Treat the repository as a release channel, not a development branch. Guideline 14 and the SVN documentation both say it: every commit regenerates the zip files for every version, which is why an update can take hours to appear. Commit when a version is ready to ship. The workflow the handbook recommends is to finish the release in trunk, set the stable tag, and copy trunk to the new tag folder with svn cp in one commit, so the tag preserves history rather than being an opaque upload.

Lab 40 minutes

Audit a plugin before submitting it. It bundles vendor/chartist.min.js, has a settings toggle labelled "Send anonymous usage statistics" that is off by default and, when on, posts a hash to your server, and declares Requires at least: 6.4 in the header while the readme says 7.0.

Write the four changes you would make, one per guideline. Then answer two questions: what must appear in the readme for the usage statistics to be allowed, and what happens on a 6.9 site that installs the plugin as declared?

Show the solution and the reasoning

The bundled library is the first change. Guideline 2 puts the licensing burden on you, so add a vendor/LICENSES.md naming Chartist, its version, its licence and its source URL, and state the licence in the plugin header. Keep the unminified source in the package or link to it in the readme, because the FAQ is explicit that minified code must not be hidden.

The statistics toggle is fine as code; the disclosure is not. Guideline 7 permits contact with external servers when consent is explicit and authorised, and an off-by-default checkbox is the shape the handbook describes. It also requires readme documentation of what is collected and how it is used: a section saying the plugin can send an anonymised identifier when the user enables the setting, naming the setting and linking the privacy policy. The toggle is consent; that section is what makes it reviewable. A default-checked toggle is the prohibited "automated collection without explicit confirmation".

The version disagreement and the 6.9 install are one problem. Requires at least must agree with reality, and the header governs installation. If the plugin needs 7.0, the header is wrong; if it works on 6.4, the readme is. On 6.9 the site sees the header's 6.4, so the upload is accepted, the plugin activates, and it then fails on whatever 7.0 feature it needs, in front of the user, with a PHP error rather than a refusal. The readme's 7.0 line never enters the decision, because the check reads the plugin file. That asymmetry is why the two lines should agree even though only one is enforced: the enforced one fails safe.

A fifth change is worth making: tag with svn cp rather than by hand, because a hand-written tag loses the history that makes the next review easy.

Official reference

The WordPress developer reference for what this lesson uses.

Progress

Mark this lesson done and it stays marked in this browser.