Skip to content
System nominalUTC --:--:--Articles 085
MODULE 05 Lesson 14 / 18 35 MIN

Working on a site someone else built

Changing a Theme You Did Not Write

Where a theme change belongs in a theme you did not build, why editing the parent theme loses the change, and how to prove the edit reached the page.

The request is small. Make the footer say the client's slogan, change the accent colour, and keep the rest of the design. It is the kind of job that used to be a five-minute functions.php edit, and it is the kind of job where an agent will confidently edit the wrong file, because the wrong file is usually the first one it finds.

There are only three places a theme change can live, and choosing between them is the whole skill. Code you added lives in a child theme. Data the site owner should control lives in the database. Behaviour that must survive a theme switch lives in a plugin. Every bad outcome in this lesson comes from putting a change in the wrong one, and the failure is usually invisible until an update lands six weeks later.

A child theme is an override, and overrides only work in one direction

A child theme does not modify its parent. It supplies files with the same names, and WordPress's template hierarchy checks the child directory first. The parent's copy of that file is still on disk, untouched, and the next parent update replaces it without affecting yours. That is the entire mechanism, and it is why the first rule is to confirm you are editing the child.

wp theme list --status=active
wp eval 'echo get_stylesheet(), " / parent: ", get_template(), "\n";'
$ wp eval 'echo get_stylesheet(), " / parent: ", get_template(), "\n";'
acme-studio / parent: twentytwentyfive

get_stylesheet() is the active theme, and it is the directory your edits belong in. get_template() is the parent, and it is the directory that gets overwritten. When those two differ, the ratio of "safe to edit" to "will be lost" is exactly one to one across the whole theme, and nothing about a filename tells you which one you opened.

The trap for agent work is that the parent theme is often the more interesting directory. It contains every template, every pattern and every style the design actually uses, so an agent asked to "change the header" will search the parent, find templates/page.html, and rewrite it there. The change works immediately. It works for weeks. Then Twenty Twenty-Five updates and the change is gone, and the diff you review will not be the one that caused it.

theme.json is a merge, and the merge has three layers

Block themes put design tokens in theme.json rather than in PHP, and the resolver merges three layers in a fixed order: WordPress's own defaults, the theme file, then the user's saved global styles from the site editor. Later layers win. A colour you add to the theme file can therefore be present in the file, present in the editor, and absent from the page.

The cheap way to see the merged result is to ask WordPress for it instead of reading the file.

wp eval '
$settings = wp_get_global_settings();
foreach ( $settings["color"]["palette"]["theme"] as $entry ) {
	printf( "%s = %s\n", $entry["slug"], $entry["color"] );
}'

Run on a child theme whose theme.json declares exactly one colour, that command printed one line:

brand = #0b5fff

One line is the surprising result. The parent theme's theme.json declares eight palette colours, and none of them survived. When both a child and a parent ship a theme.json, the child's file is the theme layer and the parent's presets are not merged into it. Add one colour to the child and you have replaced the palette, which is the difference between a change that adds a brand colour and a change that removes seven.

The rendered page is the third opinion, and it is the one that counts. On that same install, the HTML contained --wp--preset--color--brand and nothing for the parent's accent-1 through accent-3, while core's own presets such as black and white were still present. Three layers, three answers, and only the last one is what the client sees.

Ask for the last renderer, not the first resolver

wp_get_global_settings() tells you what the theme layer resolved to. It does not tell you which custom properties the page emitted, because the emitted stylesheet is a merge of that layer with the user's saved global styles and core defaults. Fetch the page and grep its HTML for the preset you changed. If the property is not in the response, no amount of correct-looking JSON in theme.json matters.

Editing a parent template is sometimes right, and never by accident

There is a legitimate version of the parent-theme edit: you copy the parent file into the child, keep the path, and edit the copy. WordPress loads the child's file because the hierarchy reaches the child directory first, and the parent stays pristine for the next update. The mechanical part is easy and the judgement is not, because you have just forked a file that the parent will continue to improve.

Three questions decide whether to fork a template at all. Does the change belong in markup, in which case a template override is honest, or in styling, in which case it belongs in CSS or theme.json and the template should stay untouched? Will the parent change this file in a way you need? And can the same result be produced by a filter, which costs no fork at all? A footer slogan, for example, is usually an option read by a small function in the child theme, not a copy of a 200-line template.

When you do fork, commit the fork on its own, before you edit it, and write down the parent version you copied from. The commit is what lets the next person answer the only question that matters two years later: has the parent file changed since we took this copy?

Prove the change reached the page before you call it done

A theme edit is finished when the page contains the change. Not when the editor previews it, not when the agent reports that the file was updated, and not when the child theme directory contains the string you asked for. You can check all three of those while the visitor sees the old value, because WordPress caches the theme layer and servers cache the response.

curl -s "https://acme.test/" | grep -c "acme-swatch"
curl -s "https://acme.test/" | grep -o -- "--wp--preset--color--brand"
grep -rn "acme_studio_footer_note" wp-content/themes/

The third command is the one that catches the expensive mistake, because it shows every directory the string appears in. If it only appears under the parent theme, the change is on borrowed time. If it appears in both, someone edited both copies and you now have two sources of truth.

Lab 35 minutes

Work on a local or staging copy of a themed site. Do not do this on production, because step 4 deliberately breaks the page.

  1. Confirm the active stylesheet and its parent. Write both down.
  2. Find a rendered string on the front page that comes from the theme. Trace it to the file that produces it and to the function that supplies it. Keep the evidence for each step: the fetched HTML, the file, the function name.
  3. Change it in the child theme. If the string comes from a parent template, copy the template into the child theme at the same path with no edits and commit that copy on its own before changing anything.
  4. Now edit the same string in the parent theme instead, fetch the page, and record what the visitor sees. Then delete your parent edit.
  5. Change one colour in theme.json. Read the merged palette with wp_get_global_settings(), then grep the fetched HTML for the custom property. Record both results.
Show what each step proves, and the trade-offs

Step 4 is the demonstration that matters, and the honest result is that it appears to work. Editing the parent theme changes the page immediately, because WordPress does not care which theme file the string came from. Nothing in the browser, the REST API or a WP-CLI command reports that the edit is temporary. The only signal is the second and third commands from the verification section: the string is in twentytwentyfive and not in acme-studio, so a parent update erases it.

Step 5 usually produces two different answers, and both are correct for their own question. wp_get_global_settings() reports what the theme layer resolved to, which is one colour, the one you added. The fetched HTML reports the custom properties WordPress actually emitted, which includes core presets and any user global styles saved in the editor. If you changed a preset that the site editor had already overridden, the API answer and the page answer disagree, and the page is right.

Two fixes in step 5 are defensible. Add only the presets you need and accept that the child theme now owns the palette, which is simple and honest for a bespoke client theme. Or keep the parent's palette and register the extra colour through a filter in the child theme, which leaves the parent's tokens intact at the cost of a second place where colours are defined.

<?php
// Child theme: add to the parent palette instead of replacing it.
add_filter( 'wp_theme_json_data_theme', 'acme_studio_add_brand_colour' );

function acme_studio_add_brand_colour( $theme_json ) {
	$data = $theme_json->get_data();

	$data['settings']['color']['palette'][] = array(
		'slug'  => 'acme-blue',
		'color' => '#0b5fff',
		'name'  => 'Acme Blue',
	);

	return $theme_json->update_with( $data );
}

The filter is the better answer when the parent theme is still receiving design work and you want it. It is the worse answer when the palette is genuinely bespoke, because it leaves a parent palette in the repository that no longer describes the site, and the next developer will read it and believe it.

Run against the same install, that filter adds its colour to whatever the theme layer already had, which is the point of using the filter instead of rewriting the file. The exact output depends on how many palette entries the parent declares, so read your own rather than trusting a number from a lesson.

$ wp eval '$s = wp_get_global_settings(); foreach ( $s["color"]["palette"]["theme"] as $e ) { printf( "%s = %s\n", $e["slug"], $e["color"] ); }'
acme-blue = #0b5fff

The instruction to give an agent on this kind of task is short: name the directory the edit belongs in, forbid edits outside it, and require the verification command in the summary. An agent that has produced a working change in the parent theme has produced the wrong change, and the output looks identical.

Official reference

The WordPress developer reference for what this lesson uses.

Progress

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