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

Shipping a theme

Testing and Distributing a Theme You Can Hand Over

Shipping a block theme: the Theme Unit Test data, theme.json validation, required files, screenshot and licence rules, and the WordPress.org review process.

Everything up to this point has been about making a theme work on your own site with your own content. Shipping is about making it survive somebody else's content, somebody else's WordPress version and somebody else's licence review, and the failures are rarely in the code you are proud of. They are in a post with no title, a font whose licence file is missing, or a theme.json key that WordPress quietly threw away three months ago and nobody noticed until a user asked why the setting does nothing.

This is the last lesson of the course, and it is the one where the earlier decisions get audited. The structure you chose, the tokens you defined and the blocks you restyled are what the test data is going to poke at, and what the reviewer is going to read.

The Theme Unit Test data is an export, and it is designed to break layouts

The file the Themes Team reviews against is the Theme Unit Test data, a WordPress eXtended RSS export imported through Tools → Import. The current file is about 609 kilobytes and contains 186 items. It is not lorem ipsum with better spacing; it is a list of specific edge cases, and reading the titles tells you what it is for:

85 chars  Taumatawhakatangihangakoauauotamateaturipukakapikimaungahoronukupokaiwhenuakitan...
68 chars  Markup: Title With Special Characters ~`!@#$%^&*()-_=+{}[]/\;:'"?,.>
54 chars  Template: Password Protected (the password is "enter")
52 chars  Markup: Title <em>With</em> <b>Mark<sup>up</sup></b>
37 chars  Template: Featured Image (Horizontal)
35 chars  Template: Featured Image (Vertical)

There are posts with no title at all, which is the one that finds themes that assume a title exists. There is one sticky post, which changes the order of the front page. There are alignment classes in numbers: sixteen alignwide, eighteen aligncenter, twelve alignleft, ten alignfull. There are twenty galleries and eight tables. There are horizontal and vertical featured images, which exist to find the themes that sized their cards for one aspect ratio. Ten comments have a non-zero parent, which is what threaded comment rendering is tested against, and a password-protected post and a title containing every problematic character are in there for the same reason.

Run it before you submit, and read the failures as a list of what your template assumed. A theme whose post title block has no wrapping group is the theme that breaks on the 85-character title, because that string will not wrap inside a flex row. A theme whose card grid hard-codes a height is the theme that breaks on the vertical featured image. Neither failure is exotic; both are the default outcome of building against the content you wrote.

theme.json is sanitised, not validated

Every theme.json file starts with a $schema URL, and it is worth being clear about what that URL does. It is a JSON Schema document, served from schemas.wp.org, and it is what gives you completion and validation in an editor. WordPress 7.0.7 does not fetch it and does not validate against it at runtime.

What WordPress does instead is sanitise. WP_Theme_JSON knows the set of keys it supports and builds a new structure containing only those keys. I tested this with a theme file containing a misspelled settings.colour, a setting named settings.totallyMadeUp, a palette entry with no name, and styles.blocks.core/quote.typography.fontSize set to the string bogus:

made-up setting kept?             no
misspelled 'colour' kept?         no
palette entry missing name kept?  no
bad typography size kept?         {"fontSize":"bogus"}

Three of those are dropped without a warning of any kind, and the page still returns 200. The fourth is kept, because fontSize is a key the sanitiser recognises and it does not check the value. That is the shape of the whole thing: unknown keys vanish silently, known keys pass through, and the CSS engine writes whatever you gave it. A typo in a key name produces a feature that does not work and no error message anywhere, which is why the schema in your editor is not a convenience but the only validator you have.

The schema is for your editor, the sanitiser is for the site

Point $schema at the version you target, for example https://schemas.wp.org/wp/7.0/theme.json, so that completion matches the release you support. A theme using a key that only exists in a later schema will pass your editor and be dropped on an older site, which is exactly the class of bug the sanitiser cannot report.

The required files are short, and the headers are checked

A block theme submission needs style.css, readme.txt, theme.json, and index.html inside a templates folder. There is no required functions.php, no required index.php, and no required comments.php; those are classic-theme requirements. The template files themselves have to be complete, with no missing or incorrect closing tags.

The style.css header is the part that fails a review on a technicality, so it is worth listing exactly. Required: Theme Name, Author, Description, Version, Requires at least, Tested up to, Requires PHP, License, License URI, Text Domain. Optional: Theme URI and Author URI, both of which have to point at something real and about your theme or you.

/*
Theme Name: M6 Verify
Author: Lab
Description: A measurement theme used to write this course.
Version: 0.1.0
Requires at least: 7.0
Tested up to: 7.0
Requires PHP: 8.3
License: GNU General Public License v2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html
Text Domain: m6verify
*/

Requires at least is the oldest main WordPress version you support, in X.X format. Tested up to is the last main version you tested, also X.X and only the number. Both are two-part versions, so neither 7.0.7 is correct there; the header speaks in major-minor releases even though the patch version is what you developed against. A readme.txt is required as well and shares several of the same fields; the guidelines note that the Resources section is where you list bundled fonts, images and scripts with their licences.

A screenshot with a phone mockup gets rejected

The screenshot is not a marketing image. It must not be bigger than 1200 by 900 pixels, its width-to-height ratio must be 4:3, and the guidelines say it must not look like an advertisement. That last sentence is the one that catches designers, because a device mockup is exactly what a marketing image looks like: a phone or laptop frame, a tilted perspective, a gradient backdrop and a caption. Add an overlay, a logo or a badge and the reviewer can reasonably ask you to change it.

The safe version is the least interesting one: a straight-on screenshot of the theme's front page or a representative template at 1200 by 900, filling the frame, with no browser chrome, no device frame and no text overlay. Avoid hateful or violent imagery, and avoid photographs of identifiable children, both of which are called out as not permitted. If the screenshot is the only thing a reviewer objects to, you have lost a review cycle over an image you can replace in a minute.

Licensing applies to the folder, not to the code

The requirement is that everything in the theme zip is under the GPL or a GPL-compatible licence, with GPLv2 or later recommended and matching WordPress itself. That covers your PHP and CSS, any bundled library, and every font file, icon set and image in the folder. The guidelines ask for copyright information for the theme, licence and copyright and source information for bundled resources, and a single file listing all of the resources. If you bundle a font, its licence has to be GPL-compatible and you have to say where it came from.

This is where a tempting shortcut turns into a rejection. Dropping a paid webfont or a stock photo into a theme and shipping it is the clearest way to fail a licence review, and "the client has a licence" is not the same as "this zip may be redistributed under the GPL". Fonts are also the resource people forget, because they arrive as files in a build step rather than as dependencies in a manifest.

There is a second rule that surprises people who also sell themes elsewhere: if you distribute themes outside the directory you may only distribute themes that are 100% GPL-compatible, and if you sell them your site has to say so somewhere easy to find.

Extendable means a child theme with no files

A child theme needs one file to exist, and it is not theme.json. It is style.css with a Template header naming the parent's folder exactly. Beyond that, everything is optional. A child's theme.json is merged over the parent's rather than replacing it, so a child can change one palette entry and inherit the rest, and its templates and parts directories override individual files by name while the rest fall through to the parent.

What that means for the theme you are shipping is that the child theme's ability to intervene is the same ability the Site Editor already has, in files instead of the database. A parent theme is extendable when its styles come from tokens rather than from selectors aimed at its own markup, and when a template it ships can be overridden by creating a file with the same name. A theme that generates its layout in a PHP function cannot be extended by any child theme, because there is no file to override.

One level only: there is no grandchild theme. The handbook describes the user's customised templates as the layer that plays that role, because the changes live in the database.

The review process reads the update, not the theme

Anyone with a WordPress.org account can upload a theme zip from the WordPress.org Themes page, and updates go through the same upload. The Themes Team reviews against the Theme Unit Test data, so a reviewer is looking at your theme with that content imported. The guidelines state the teeth: a theme with three or more distinct issues may be closed as not approved, and you can resubmit after fixing them.

First-time submissions are queued and reviewed as a whole. Updates are different in two ways. Version numbering is enforced, so the new directory name has to be the new version and it has to be higher than the last. And once a theme is live, the update is generally checked as a diff against the previous version, so you can ship a bug fix without re-explaining the parts of the theme that have not changed. That changes what you owe a reviewer: on a first submission you are demonstrating that the theme is well built, and on an update you are demonstrating that nothing you changed broke the reason it was approved.

Two pieces of process are worth knowing before you queue. Only one new theme may be submitted at a time, unlimited updates are allowed. And the accessibility-ready tag is a separate review by the accessibility team: the tag is not a self-certification, and adding it without passing that review can get a theme suspended from the directory.

The part that is genuinely different from a plugin is the last mile. A plugin you own the update channel for: you host the zip, the site checks a URL you control, and you decide when a release is ready. A theme in the directory is distributed by WordPress.org, reviewed before it is allowed in, and updated by a commit to the theme's SVN repository, where a version directory is immutable and a mistake is corrected by shipping the next version. You are handing the theme over, and the handover is the product.

Lab 40 minutes

Download the Theme Unit Test data and import it into a local install with your theme active. Then audit three things.

First, find the post with no title and the post with the 85-character title, and look at how your archive and single templates render them. Second, delete every palette entry from your theme.json except one, reload the site, and confirm that the colours your CSS expects are gone; then put them back and check the editor still lists them. Third, write the style.css header and the readme.txt Resources section for your theme from scratch, including every font and image file in the folder.

Finish by listing every file in the theme directory and marking each one as required, optional or unnecessary.

Show the solution and the reasoning

The untitled post is the test most themes fail, and the failure is usually structural rather than visual. A post title block renders an empty h1 or h2 element when the title is empty, which puts an empty heading in the document outline and an empty link on an archive. The long title is the other half: it has no spaces, so a layout that relies on whitespace breaks will overflow its container, and overflow-wrap: anywhere on the title selector fixes it everywhere.

Removing palette entries is the experiment that shows where your colours actually come from. Anything you wrote as var(--wp--preset--color--accent) stops resolving, which is visible as text and backgrounds falling back to inherited values, while anything you wrote as a hex literal keeps working. That is the reason to use preset variables: the user can change the palette in the Site Editor and the theme follows. A theme whose CSS is full of literals is not broken, but it is not themeable either.

The header and readme exercise is mostly about admitting what is in the folder. A block theme I would submit has style.css, readme.txt, theme.json, templates/index.html and any other templates the theme ships, parts/, patterns/ and the stylesheet and font files those reference. Unnecessary in a block theme: index.php, comments.php, header.php, footer.php, and any copy of jQuery or another library already in core. Optional but worth shipping: a functions.php for the per-block stylesheet registrations, and a screenshot.png at 1200 by 900.

The review-shaped conclusion is that none of this is about the code being clever. The unit test data finds assumptions, the sanitiser hides typos, the headers and the screenshot are checked by rule, and the licence covers every byte in the zip. A theme that passes all four is not necessarily good, but a theme that fails any of them is not shippable, and the four are cheap to check before the upload and expensive to discover afterwards.

Six modules ago this course started by emptying a directory and asking what a block theme actually is, and the answer changed shape as the material went in. The template hierarchy decided which file answered a URL; theme.json turned design decisions into tokens and then into custom properties; blocks made templates into data the user could rearrange. What the last module adds is the observation that all of those decisions are visible from outside the theme: accessibility, page weight, test data and licensing are the same architecture read by a screen reader, a network panel, somebody else's content and a reviewer.

Progress

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

Course complete

That is the last lesson of WordPress Block Themes and Full Site Editing. If the material was useful, the practice is what makes it stick: rebuild the final project from an empty directory without looking back at the lesson.