Shipping
The wordpress.org Submission and SVN
What the SVN working copy in this project contains, how a release is cut, and which directory guidelines the shipped plugin draws a reviewer's attention to.
The source tree for this plugin contains a directory called SVN/ with
twelve PNGs, a trunk and a tags folder. It is not a mock. Its .svn metadata
points at https://plugins.svn.wordpress.org, the tag
tags/1.2.0 is at revision 3556106, and the working copy root is at 3556093.
That is a wordpress.org plugin-directory checkout, and reading it tells you more about how
releases work than any tutorial, because you can see what the last release published.
What it does not contain is a preview of the submission. The 1.2.0 tag already exists, so this plugin has been through review at least once. The licence rule, the queue, the things reviewers send back and the reason a paid "Upgrade to Pro" page is either fine or fatal are all part of shipping that the code does not document.
The working copy has three directories, and only two hold code
A repository is created for you at
https://plugins.svn.wordpress.org/your-plugin-slug and arrives with
trunk/, tags/ and assets/. The handbook notes that
branches/ is no longer created by default because it went unused. Here is what
this working copy holds:
SVN/
├── .svn/ metadata: repository URL, revisions, pristine copies
├── assets/
│ ├── banner-772x250.png plugin page banner
│ ├── banner-1544x500.png high-DPI banner
│ ├── icon-128x128.png icon shown in search results
│ ├── icon-256x256.png high-DPI icon
│ ├── icon.svg vector icon
│ ├── icon-1024.png 1,207,510 bytes, source asset
│ └── screenshot-1..6.png six screenshots with no readme section to show them
├── tags/
│ └── 1.2.0/ a full copy of the shipping code, revision 3556106
└── trunk/ the shipping code, revision 3556106
├── assets/css, assets/js, assets/img
├── includes/*.php
├── languages/*.pot
├── templates/*.php
├── readme.txt
└── sifero-courier-timeline-for-woocommerce.php
The main plugin file must be at the root of trunk/. Putting it one folder
deep, trunk/my-plugin/my-plugin.php, breaks downloads; subfolders for includes
are fine, and this plugin uses them the way the handbook expects. assets/ is a
sibling of trunk/ for a reason. Screenshots and banners never ship inside the
plugin ZIP, so putting them in trunk would make every install larger for images the user
never sees.
You can read the two revisions without an SVN client. The working copy's SQLite database holds the node table:
sqlite3 SVN/.svn/wc.db \
"select local_relpath, revision from NODES where op_depth=0 and kind='file'
and (local_relpath like 'trunk/readme%' or local_relpath like 'tags/%/readme%');"
tags/1.2.0/readme.txt|3556106
trunk/readme.txt|3556106
Same revision for both files, and that matters. The directory reads
trunk/readme.txt only for the Stable tag line, then serves
everything else from the tag folder that line names. If the two readmes differ, the
directory can point users at a tag whose readme does not describe it.
A release is one copy command and two version bumps
Start from a checkout of the repository root, not from trunk. The handbook's example is a checkout into a local directory, then a copy from trunk to the tag:
svn co https://plugins.svn.wordpress.org/sifero-courier-timeline-for-woocommerce sfct-svn
cd sfct-svn
svn cp trunk tags/1.2.0
svn ci -m "Tagging version 1.2.0"
That is the whole mechanism, and the details are where people lose a release. The version
bump happens in two files before the tag exists: the Version header in the main
PHP file and the Stable tag line in trunk/readme.txt. Both must say
1.2.0. svn cp preserves history, so the tag is a copy inside the repository and
the folder is created as the files appear in it.
What you do not do is edit the tag afterwards. Editing
tags/1.2.0/readme.txt to fix a typo means the directory rebuilds the 1.2.0 ZIP
with different content than the people who already downloaded it, and those users have no
version number to compare. If the bug is in the readme only, the temptation is strong and
the answer is still no: cut 1.2.1.
The other half of the discipline is commit frequency. SVN here is a release repository,
and every commit triggers a rebuild of every ZIP. The handbook calls out rapid-fire commits
that only tweak minor things, including the readme, as strain on the system and gaming of the
recently-updated list, with one documented exception: readme commits that only declare
support for the latest WordPress release. The 1.2.1 commit for this plugin should therefore
carry the version bump, the changelog entry and the corrected Tested up to
together.
The licence is the first thing a reviewer checks
Guideline 1 cannot be negotiated: plugins must be compatible with the GNU General Public License. The handbook's summary says that if a licence is not specified the code is treated as "GPLv2 or later", and that is also the recommended choice because it matches WordPress. This plugin declares it in both required places:
# plugin header
* License: GPLv2 or later
* License URI: https://www.gnu.org/licenses/gpl-2.0.html
# readme.txt
License: GPLv2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html
Both places matter. The common-issues list has an entry for a missing GPL-compatible licence and a separate one for a mismatched declaration, and it requires a single declared licence for your own code. Code from elsewhere can be under a different GPL-compatible licence, but it has to be documented.
This plugin is the clean case. It bundles no third-party library: twenty-seven files of
GPL PHP, CSS and JavaScript that call WordPress and WooCommerce APIs. No
vendor/ directory, no minified bundle from a CDN, no font. It owes no licence
statement beyond its own.
Bundling changes that. Guideline 1 says included libraries, code and images must be
compatible, and guideline 2 puts the burden on you: confirm the licensing of every included
file before uploading, and if you cannot validate a library's licence you cannot use it. The
common-issues list adds two more entry points, a library no longer maintained and one that
is out of date, both treated as security problems. The practical format is a licence block in
the file header or a LICENSE.txt beside it, plus a readme line naming the
library, its version and its licence.
Minified code is not a shortcut
Guideline 4 requires code to be mostly human readable, and the common-issues list is blunt about build output: if you ship compressed files, make the source publicly available and document it in the readme.
What reviewers reject, and how long the queue takes
The numbers in the developer FAQ are specific. A submission should get an initial review within four weeks, and only one submission per developer is allowed at a time. If the code is small and correct, the FAQ's phrasing is approval "within fourteen days of initial review". When the team sends fixes to make, they aim to reply within ten business days, and a review left untouched for three months is rejected. Reply to the email rather than resubmitting; the FAQ says not to resubmit a plugin rejected for any other reason.
The rejections cluster around a short list, and this plugin is on the right side of almost
all of it: headers missing or split across files, an incomplete readme, no GPL-compatible
licence, a stable tag that does not match the plugin version, SQL built without
$wpdb->prepare(), generic function and option names that collide with other
plugins, and an update checker that phones home.
That last one deserves care because it usually arrives through a dependency. Plugins may
not contact other servers for updates, because wordpress.org provides that service, and they
must not interfere with the built-in updater. Guideline 8 adds that serving updates or
installing premium versions of the same plugin from other servers is not allowed. An
Update URI header pointing at your own update server is the documented way to
update a plugin hosted elsewhere, not a loophole for a directory plugin.
This plugin's free build makes no outbound HTTP requests. No wp_remote_get,
no curl_init, no remote CSS or JavaScript, no QR image service: the changelog
records that external QR loading was removed from the wordpress.org build. The courier
companies table still carries api_url, api_key, token,
auth_type, timeout and custom_headers columns, but
nothing in this build reads or writes them. They came across from the Premium codebase. A
reviewer who greps for api_url finds a credential-shaped column with no call
site, which produces a question you then answer in email.
An upgrade prompt is allowed, with conditions
The plugin ships an admin page called "Upgrade to Pro" at
sfct-optional-addon, with a price strip, a feature comparison and an external
link. That is permissible, and the rule that permits it is guideline 5. Trialware is not
permitted: no functionality locked behind payment, no feature that switches off after a
trial, no sandbox-only API access. But the same guideline says upselling is acceptable
"provided it falls within bounds of guideline 11", and it recommends hosting premium code in
a separate add-on outside wordpress.org, which is what the premium build here is.
Guideline 11 holds the conditions. Upgrade prompts must be limited in scope, used sparingly, and appear contextually or only on the plugin's own settings page. Advertising in the dashboard should be avoided, and any site-wide notice must be dismissible. Links to your own sites are encouraged; tracking referrals through them is not, and guideline 7 classifies that as contact with an external server without consent.
Measured against that, this page is on the right side. It is a submenu item under the
plugin's own top-level menu, not a dashboard widget and not an admin notice, so it cannot
follow a user to unrelated screens. It links out with rel="noopener noreferrer"
and no referral parameters, and it states that the free build keeps working without the
add-on, which guideline 9 separately requires because implying users must pay to unlock
included features is prohibited. The one thing to fix is the readme's "Upgrade to Pro"
section, which sits inside the public description page that guideline 12 governs. That
guideline permits links to required products in moderation and forbids keyword stuffing. One
section is moderation; a second in the FAQ is a pattern.
Guideline 10 covers the front end. Credit links on public pages must be optional and off by default. The public tracking template renders courier data and no "powered by" footer, so there is nothing to remove, but a branded footer added later needs a setting that defaults to off.
Prepare the next release of this plugin without touching the existing tag. Work on a
copy of the SVN/ working copy.
First, write the commit plan as an ordered list: which files change, which command creates the tag, and which line in which file makes the directory serve the new version. For each step, say what breaks if it happens in the wrong order.
Second, do the review yourself. Produce two lists from the source: findings that come from the Plugin Handbook's common-issues page, and findings that come from the detailed guidelines. Cite the guideline number or section name for each, and say whether the fix is code, a readme edit, or a licence statement.
Third, audit the licence position. Name every file in the free build that is not PHP you wrote or a WordPress API call, and for each say what licence statement it needs and where it goes. If the list is empty, show the command you ran to establish that.
Show the solution and the reasoning
The commit plan has four steps and the order is the lesson. Bump the
Version header. Bump Stable tag in trunk/readme.txt
to the same number and add the changelog entry in the same pass. Run
svn cp trunk tags/1.2.1 from the repository root. Commit with a message that
names the version. Tag first and the directory reads a stable tag pointing at a folder
whose readme claims a different version, which the handbook warns can break updates.
The common-issues pass is short here. No update checker, no remote assets, no bundled
library, SQL that uses $wpdb and dbDelta(), names prefixed
sfct_ throughout. The readme is incomplete rather than missing, and the gap is
the changelog entry for the shipped version, because a plugin with dependencies or external
services is expected to document them.
The guideline pass is where the judgements are. Guideline 5 permits the upgrade page, 11 constrains it, 7 forbids the referral tracking that would make it a violation, and 10 constrains any credit link on the public template. Guideline 13 is worth checking once, because it forbids bundling libraries WordPress ships: the free build enqueues its own script with no jQuery dependency, so it passes.
The licence audit should produce an empty list, and the honest way to show that is a command rather than an assurance:
find sifero-courier-timeline-for-woocommerce -type f \
| grep -vE '\.(php|css|js|pot|png|txt)$|\.DS_Store'
find sifero-courier-timeline-for-woocommerce -type d -name vendor
Both return nothing: twenty-seven files, no third-party code, so the declared GPLv2-or-later licence covers everything and no additional statement is owed. Run the check anyway, because a release or two later somebody adds a QR library and this is the only thing that would have noticed.
One limitation. Two plugins with the same code can come back with different requests, so check the guidelines before submitting rather than arguing about them afterwards.
Official reference
The WordPress developer reference for what this lesson uses.
Progress
Mark this lesson done and it stays marked in this browser.