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.
Calling home needs consent, and trialware is a hard no
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.
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.