Command Palette

Search for a command to run...

What Stable Tag Does, and Why the Wrong One Ships Nothing

What Stable Tag Does, and Why the Wrong One Ships Nothing

T
Toolz Team
|Sep 25, 2026|9 min read
Prefer Toolz.dev on Google

WordPress Readme Generator

Write a plugin readme.txt the WordPress.org directory parses, with a live preview of the listing it produces and a download when it is right.

Use the WordPress Readme Generator

The first plugin I put on WordPress.org shipped nothing for nine days. I had committed the code, tagged the release, watched the listing update with my new description, and told people it was out. Downloads stayed flat because the download button was serving an empty tag folder: my readme.txt said Stable tag: 1.0.1 and the tag I had actually created was 1.0. Nothing warned me. The directory did exactly what I told it to.

That line is the highest-stakes field in a WordPress plugin's readme.txt, and it is one of several that fail quietly rather than loudly. This guide covers what the directory does with each part of that file, in the order the damage tends to happen. The WordPress Readme Generator builds the file with these rules attached to the fields they apply to.

TL;DR: Stable tag names the version the directory actually serves. It is read from trunk/readme.txt, and it points at a folder under /tags/ that must exist and must contain the release. If it names a version that is not there, users get nothing; if it still names an old one, they get old code no matter what you committed. Tested up to controls the compatibility warning on your listing, Tags indexes only the first five, and the short description is cut at 150 characters.

What does Stable tag actually control?

A plugin in the directory lives in Subversion, with trunk/ for current development and tags/<version>/ for releases. When someone clicks Download, the directory does not send them trunk. It reads trunk/readme.txt, finds Stable tag, and serves tags/<that value>/.

Three consequences follow, and all three bite real releases:

  • The readme that decides this is the one in trunk. Editing the readme inside a tag folder changes nothing about what ships.
  • The tag has to exist. A Stable tag naming a folder that was never created means the download is empty or fails.
  • Trunk is not what users receive. You can commit to trunk all week; until the stable tag moves, the released code is whatever the old tag holds.

The Plugin Handbook states the rule plainly, and it is worth reading once properly rather than copying a readme from another plugin and editing the fields. The one exception worth knowing: Stable tag: trunk does mean "serve trunk", which is legal and is how a handful of plugins release. It also means every commit to trunk is immediately live for every user, which is why almost nobody should do it.

There is a second half to the release that the readme does not control at all. The version in Stable tag has to match the Version: header in your main plugin PHP file, because that is what an installed site compares against to decide whether an update exists. Ship a tag whose plugin header still says the previous number and the directory offers an update that installs something that claims to be the version already present.

What each header line does to your listing

The header block is nine lines of plain Key: value and every one of them changes something visible.

Line What it does What goes wrong
Stable tag Picks the version served Wrong value ships nothing, or ships old code
Requires at least Minimum WordPress version Too high blocks installs that would work
Tested up to Compatibility statement Falling behind shows an "untested" warning
Requires PHP Minimum PHP version Too low lets incompatible sites install and fail
Tags Directory keywords Only the first five are indexed
Contributors Links WordPress.org profiles A typo silently credits nobody
Donate link Sidebar donate button Absent is fine, broken is not
License / License URI The licence Must be GPL-compatible to be listed

Tested up to is the one that costs downloads silently. When it falls more than a few core releases behind, the listing shows a notice telling visitors the plugin is untested with their version of WordPress, and a plugin that looks abandoned gets installed less regardless of whether it works. Updating that line is a readme commit to trunk and takes a minute, which is the cheapest maintenance in the ecosystem.

Tags indexes five. A sixth tag is not an error and produces no warning; it is simply dead text in your file. Choose the five people actually type.

How is the description split, and why does it matter?

There are two descriptions in a readme and they do different jobs.

The short description is the single block of text between the header lines and the first == Section ==. It is capped at 150 characters, and past that the directory truncates it in search results and on the plugin card, usually mid-sentence. This is the line most people read before deciding whether to click, which makes 150 characters the most valuable real estate in the file.

The long description is the == Description == section and it becomes the body of your listing page. It takes a subset of Markdown: headings, bold, italic, lists, links. Raw HTML is stripped. Tables, fenced code blocks with syntax highlighting, and the more exotic Markdown do not render, so a readme that looks right in a Markdown previewer can still look wrong on WordPress.org.

The other sections each map to a tab on the listing:

== Description ==            the main body
== Installation ==           the Installation tab
== Frequently Asked Questions ==   the FAQ tab, one "= question =" per entry
== Screenshots ==            numbered captions, matched to files in /assets/
== Changelog ==              one "= version =" block per release, newest first
== Upgrade Notice ==         the short line shown inside wp-admin on update

Upgrade Notice deserves more attention than it usually gets. It is the only text most users see before clicking update: one or two lines, in the update prompt, explaining why this release matters. A security fix, a breaking change, a new PHP requirement. Left empty, the update is just a number.

Where do the banner and icon live?

Not in readme.txt. This trips up almost everyone the first time, because the readme is where every other piece of listing content comes from.

Images live in an /assets/ directory in SVN, beside trunk and tags, and they are matched by filename:

File Purpose Size
banner-772x250.png Listing header 772 x 250
banner-1544x500.png Retina header 1544 x 500
icon-128x128.png Search results icon 128 x 128
icon-256x256.png Retina icon 256 x 256
screenshot-1.png First screenshot any

The screenshots connect back to the readme by number. screenshot-1.png is described by the first line under == Screenshots ==, screenshot-2.png by the second, and so on. A caption with no matching file shows nothing; a file with no caption shows an unlabelled image.

What does a good changelog look like?

One block per release, newest at the top, each line saying what changed from the user's point of view:

= 3.2.5 =
* Fixed: admin menu order lost after a role change
* Improved: login customizer previews without saving

= 3.2.4 =
* Added: per-role dashboard widget visibility

"Bug fixes and improvements" tells a user nothing and tells a reviewer less. The changelog is also where anyone deciding whether to trust your plugin looks first: a steady list of specific entries reads as maintenance, and a gap of two years reads as abandonment whether or not the code still works.

Long changelogs are fine to trim. Keep the recent releases in readme.txt and move the rest to a changelog.txt; the directory reads the trimmed one and the history stays in the repository.

Checking the file before you ship it

The mechanical checks are quick: does Stable tag match a tag folder that exists, does it match the Version: in your plugin header, is Tested up to current, is the short description under 150 characters, are there five tags or fewer.

WordPress.org has an official readme validator that parses a file and reports what it could not read, which is worth running once per release. The Readme Generator takes the other direction: it builds the file from fields, states each of these limits next to the field it applies to, previews the listing the file will produce, and reads an existing readme.txt back into the form so an old plugin's file can be brought up to date without retyping it.

If you are looking at other people's plugins rather than publishing your own, the WordPress Plugin Detector lists what a page loads, and the theme detector reads the theme behind it.

Comments

0 comments

0/2000 characters

No comments yet. Be the first to share your thoughts!