Skip to content
System nominalUTC --:--:--Articles 085
MODULE 03 Lesson 07 / 18 30 MIN

Building a theme by prompt

Scaffolding a Classic Theme

What WP-CLI's theme scaffolder emits in 2026, why no theme.json appears, and how to read twenty generated PHP files before you ship them.

Type wp scaffold _s acme-theme into an agent session and you will get a theme. Ask the agent for a "modern block theme with theme.json" and the agent, being helpful, will often run the same command and then tell you it built you a block theme. The command does not have an option for that. It has never had one.

So the first thing to settle is what the scaffolder is. It is a generator for Underscores, Automattic's starter classic theme, and Underscores has been a classic theme since it was written. The output is twenty PHP files, a style.css, and no theme.json anywhere. If you want a block theme you are going to write one, and the next lesson builds it from theme.json outward.

That is not an argument against scaffolding. Classic themes are still correct for a lot of sites, and the PHP template hierarchy is the right model for them. The argument is that the choice has to be made by you, before the prompt, because the tool makes it for you by default and will not tell you it did.

The scaffolder refuses to run until WordPress is installed

This catches people first. Running the command in an empty directory fails with a message about WordPress, not about themes:

$ wp scaffold _s acme-theme
Error: This does not seem to be a WordPress installation.
Pass --path=`path/to/wordpress` or run `wp core download`.

The scaffolder loads WordPress to read its own defaults, so it needs an install, not just the files. In the lab that means a database and wp core install before anything else. I built a fresh WordPress 7.0.7 for this lesson and then ran the scaffolder against it:

$ wp scaffold _s acme-legacy --theme_name="Acme Legacy" --author="Acme" --activate
Success: Created theme 'Acme Legacy'.
Success: Switched to 'Acme Legacy' theme.

Two successes, and no hint about what kind of theme arrived.

Twenty files, and not one of them is theme.json

Here is the whole tree the command produced:

.editorconfig   comments.php   inc/           sidebar.php
.eslintrc       composer.json  index.php      single.php
.stylelintrc.json  footer.php  js/            style-rtl.css
404.php         functions.php  languages/     style.css
LICENSE         header.php     package.json   template-parts/
README.md       page.php       phpcs.xml.dist
archive.php     readme.txt     screenshot.png
                search.php

Count the PHP files and you get twenty. There is a template-parts/content.php and its friends, a sidebar.php, the six classic templates in the hierarchy, and a functions.php that enqueues a stylesheet. There is no templates/ directory, no parts/ directory, and no theme.json.

You do not have to take the file listing on trust for the last part. WordPress answers the question directly:

$ wp eval 'var_dump( wp_get_theme( "acme-legacy" )->is_block_theme() );'
bool(false)

WP_Theme::is_block_theme() checks for the templates/index.html file that only block themes have. Twenty PHP templates and no HTML templates means false, and that value decides which of two entirely different rendering paths the site takes. A theme cannot be half of each.

The style.css header is a museum piece

Open the generated style.css and read the header rather than the CSS. This is what came out of WordPress 7.0.7's own toolchain:

/*!
Theme Name: Acme Legacy
Theme URI: http://underscores.me/
Author: Acme
Author URI: http://underscores.me/
Description: Custom theme: Acme Legacy, developed by Acme
Version: 1.0.0
Tested up to: 5.4
Requires PHP: 5.6
License: GNU General Public License v2 or later
...
*/

Read Tested up to: 5.4 again. WordPress 5.4 shipped in March 2020. Requires PHP: 5.6 is a version that reached end of life in December 2018. Neither line is a lie the agent told you; both are what the template ships with today, and both end up in every theme scaffolded with this command.

What the header lines actually do

Nothing executes differently because of them. WordPress reads Requires PHP for the admin's compatibility notice and Tested up to for the theme directory listing, and neither changes how a template renders. The reason to fix them is that they are the first thing a reviewer or a plugin compatibility check reads, and they are wrong. It is also a useful signal: a file that declares a 2020 ceiling was not reviewed by anyone in 2026.

An agent left alone will not notice any of this

Run the same prompt against the same scaffolded theme and the failure is quiet. Ask for "a responsive theme with a hero section" and the agent edits header.php, adds a front-page.php, and reports success. Every edit is correct classic-theme work. The theme is still a classic theme, the style.css header still says 5.4, and if you asked for theme.json the agent may write one anyway. A theme.json in a classic theme is not ignored, but it takes a different path: WordPress applies the presets and styles, and the PHP templates still render the page. You get half of the block-theme behaviour and none of the Site Editor.

The reliable check is a file that must be absent and a file that must be present. This is the version I ask an agent to run after any theme work, because it fails loudly in both directions:

$ test -f wp-content/themes/acme-legacy/theme.json && echo "theme.json present"
$ test -d wp-content/themes/acme-legacy/templates && echo "block templates present"
$ find wp-content/themes/acme-legacy -name "*.php" | wc -l
      20

Three commands, three facts, no interpretation. Twenty PHP files and no theme.json is a classic theme. A templates/index.html and a theme.json is a block theme. Anything else is a hybrid, and hybrids are where the support tickets come from.

Which path a project should take

The decision is not about novelty. It is about who edits the site afterwards.

Choose classic whenChoose block when
The site is a custom layout that no editor will touch, or the theme is one file of a site you inherited. An editor needs to change templates, headers, spacing or colours without a deploy.
You need to control markup at the PHP level, for example a template that talks to an external API during render. The design system lives in theme.json: palettes, font sizes, spacing scales, block styles.
A plugin or a page builder owns the front end and the theme is mostly a wrapper. You want style variations and per-block styles that ship with the theme.

The block path costs more upfront, because theme.json is a schema with version-specific behaviour and the agent has less signal about it than about header.php. The classic path costs more later, because the moment an editor wants to move a header they are filing a ticket.

Whichever you pick, pick it before the prompt and put it in the prompt as a constraint with a verification step. "Scaffold a classic theme with wp scaffold _s, then correct Tested up to and Requires PHP to the versions we support, and show me the header" is a prompt an agent can fail. "Build me a theme" is not.

Lab 25 minutes

Scaffold a theme against your local WordPress and audit it before you change a line.

wp scaffold _s acme-audit --theme_name="Acme Audit" --author="You" --activate

Then answer four questions with commands rather than with reading:

One: how many PHP files did it create? Two: is it a block theme, according to WordPress? Three: what are the two version lines in the style.css header, and what are the current versions of WordPress and PHP on this machine? Four: which files would you have to delete to make this a block theme, and which would you have to add?

Write the answers down before you fix anything. The fourth question is the one that tells you which path you are actually on.

Show the solution and the reasoning

The counts depend on your WordPress release, so the commands matter more than my numbers. Against WordPress 7.0.7 the answers were:

$ find wp-content/themes/acme-audit -name "*.php" | wc -l
      20
$ wp eval 'var_dump( wp_get_theme( "acme-audit" )->is_block_theme() );'
bool(false)
$ grep -E "Tested up to|Requires PHP" wp-content/themes/acme-audit/style.css
Tested up to: 5.4
Requires PHP: 5.6
$ wp core version; php -r 'echo PHP_VERSION, "\n";'
7.0.7
8.5.6

Question four is the interesting one, and the honest answer is that converting in place is the wrong move. To make the directory a block theme you would delete all twenty PHP templates, the inc/ directory, the template-parts/ directory and the enqueuing in functions.php, then add templates/index.html, templates/single.html, parts/header.html and a theme.json. That is not a conversion; it is a new theme that happens to share a style.css.

So the reasoning is: audit the scaffold, keep the pieces that are genuinely reusable, and start the block theme in a new directory rather than mutating this one. The reusable pieces are the licence, the phpcs.xml.dist, the composer.json if you use it, the screenshot, and the text domain conventions in the existing PHP. The templates and the version header are not reusable.

One more thing worth noticing while you are here. wp scaffold _s generated composer.json and phpcs.xml.dist without being asked. Read the phpcs.xml.dist before you trust it: the copy this run produced still configures PHPCompatibilityWP with testVersion 5.6- and minimum_supported_wp_version 4.6, and it names the text domain as my-plugin. A linter configured for a floor you do not support will pass code you should not ship, and it will not warn you, because from its point of view 5.6 is the target.

Progress

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