Open the child theme folder on a typical WordPress site and you find two files. A style.css with a header comment and maybe thirty lines of CSS, and a functions.php holding one enqueue snippet copied from a tutorial. No overridden templates. Nothing else.
That child theme is not protecting anything. It exists because most guides on customising WordPress open with “first, create a child theme,” and the instruction gets followed before anyone asks what it is meant to protect. It adds a second stylesheet request to every page load and a second theme to keep track of.
Child themes are the right tool for one job and they are very good at it. The advice to build a WordPress child theme before you touch anything has outrun that job by a wide margin, and the cost of an unnecessary one is not zero. It gets paid slowly, every time the parent theme updates.
What a child theme actually is
Mechanically it is almost nothing. A child theme is a directory in wp-content/themes/ containing a style.css whose header comment names a parent theme. That is the entire requirement. Everything else, including functions.php, template files, screenshot.png and theme.json, is optional.
Two fields in that header carry the weight: Theme Name and Template. The Template value is the parent theme’s folder name, not its display name, and it has to match exactly. Twenty Twenty-Four lives in a folder called twentytwentyfour, so that is what goes in the header.
/*
Theme Name: Twenty Twenty-Four Child
Template: twentytwentyfour
Version: 1.0.0
Text Domain: twentytwentyfour-child
*/
Save that as style.css inside wp-content/themes/twentytwentyfour-child/ and the child theme appears under Appearance > Themes, ready to activate. Get the Template line wrong and WordPress flags the theme as broken with the message “Template is missing,” telling you to install the parent theme it cannot find.
Once the child is active, two path helpers stop meaning the same thing. get_stylesheet_directory() points at the child. get_template_directory() points at the parent. Most “why isn’t my override loading” questions trace back to one of those two being used where the other was needed.
Additive functions, replacing templates
Here is the part most tutorials skip, and it is the source of most of the confusion around child themes.
functions.php is additive. WordPress includes the child’s functions.php first and the parent’s immediately after. Both run. You are not replacing the parent’s functions file, you are running ahead of it.
Template files are replacing. Put single.php in the child and WordPress uses it instead of the parent’s single.php. The parent’s version is never loaded, never merged, never consulted. The same goes for header.php, footer.php, archive.php and any partial the parent pulls in with get_template_part().
That load order has a practical consequence. Because the child runs first, you cannot simply redeclare a function the parent defines. PHP throws a fatal “cannot redeclare” error when the parent file loads afterwards. Well-built parent themes wrap their overridable functions in if ( ! function_exists( 'theme_function_name' ) ) precisely so a child can define its own version first. If the parent did not do that, the function is not overridable and you need a filter or an action removal instead.
The replacing behaviour has a limit too. Overrides only work where the parent goes through the standard lookup, meaning locate_template(), get_template_part(), get_header() and the template hierarchy itself, all of which check the child directory before the parent. If the parent theme does something like include get_template_directory() . '/inc/helpers.php', that path is pinned to the parent and no file in your child theme will ever be picked up in its place.
Loading the parent stylesheet correctly
Old tutorials tell you to put @import url("../parenttheme/style.css"); at the top of the child’s style.css. Don’t. The browser has to download and parse the child stylesheet before it discovers the import, then start a second request, so the two files load in series instead of in parallel and rendering is blocked for the length of both. The theme handbook has advised against it for years.
Enqueue the parent stylesheet from the child’s functions.php instead, and declare the child stylesheet as depending on it so the order is guaranteed.
<?php
add_action( 'wp_enqueue_scripts', 'childtheme_enqueue_styles' );
function childtheme_enqueue_styles() {
$parent = wp_get_theme( get_template() );
wp_enqueue_style(
'parent-style',
get_parent_theme_file_uri( 'style.css' ),
array(),
$parent->get( 'Version' )
);
wp_enqueue_style(
'child-style',
get_stylesheet_uri(),
array( 'parent-style' ),
wp_get_theme()->get( 'Version' )
);
}
get_parent_theme_file_uri() always resolves to the parent directory, which is what you want here, and get_stylesheet_uri() always resolves to the active theme’s style.css, which is the child’s. Passing each theme’s own version string as the fourth argument means the cache-busting query string changes when you bump the version instead of never changing at all.
One caveat before you paste that in. Plenty of parent themes already enqueue their stylesheet with get_stylesheet_uri(), which under a child theme resolves to the child’s file. In that case the snippet above loads the same file twice under two handles, because WordPress deduplicates by handle, not by URL. Look at what the parent registers first. If it already has a handle for its stylesheet, drop the first wp_enqueue_style() call and list that existing handle as your dependency.
The one case that genuinely needs a child theme
You are overriding template files in a theme that somebody else maintains and that receives updates.
That is the whole case. If the single-post template has to output an author box in a position no hook exposes, you copy single.php, or whatever partial the theme uses, into the child and edit your copy. When the parent updates, your copy survives. Edit the parent directly and the update overwrites the file with no undo.
When you copy a template into a child theme, mirror the parent’s directory structure exactly. A theme that calls get_template_part( 'template-parts/content', 'single' ) looks for template-parts/content-single.php, and it checks the child directory for that path before falling back to the parent:
my-theme-child/
style.css
functions.php
template-parts/
content-single.php
Drop that same file in the child theme’s root and nothing happens. The lookup is path-sensitive and there is no error when it fails to match. You keep seeing the parent’s output and wonder why.
If you have decided you need one, there is no reason to write the boilerplate by hand. Fill in the parent directory below and the generator builds the theme.
It writes the style.css header in the order WordPress reads it, the functions.php with the enqueue that matches your case, block theme or classic, and a screenshot, then hands the whole folder over as a zip. No account, no upload, no generator service in between.
Child theme generator
Name the parent theme folder and get a child theme back that works as it is: style.css with the header in the order WordPress reads it, functions.php with the right enqueue, and a screenshot drawn here. The ZIP is built in this browser tab, nothing is uploaded and no generator service is involved.
The folder name under wp-content/themes, not the name shown under Appearance. Lower case, a to z, 0 to 9 and hyphens. It has to match the folder exactly, folder names are case sensitive on most servers.
Derived from the theme name until you edit it yourself.
A template or part in the child replaces the parent version of it completely. The two stubs are there to start from, delete whichever you do not intend to change.
Drawn here at 1200 by 900, the size the theme browser under Appearance expects.
- Upload the ZIP under Appearance, Themes, Add New, Upload Theme. Over SFTP, copy the unzipped folder into wp-content/themes/ instead.
- Activate the child theme, and leave the parent installed and updated. A child theme cannot run without its parent.
- Redo the settings by hand. Customizer options, widgets and menu locations are stored per theme, so they do not follow the switch. Write down what you have set before you activate.
Where PHP snippets belong
If your entire customisation is a handful of add_filter() and add_action() calls, a child theme is the wrong container. A small site-specific plugin is better in three ways, and the differences are not academic.
It survives a theme switch. A child theme’s functions.php stops running the moment you activate a different theme, and everything in it goes quiet at once. Shortcodes stop resolving, custom post types vanish from the admin, registered image sizes stop being generated. A plugin keeps running.
It can be switched off. If a snippet in a plugin causes a fatal error, you deactivate the plugin or rename its folder over SFTP. A fatal error in the active theme’s functions.php takes the whole front end down along with the admin, and you are into recovery-mode emails and file manager surgery to get back in.
And it separates concerns honestly. A theme file is for presentation, while behaviour that should outlive your current design belongs elsewhere. That boundary, what belongs in functions.php and what belongs in a plugin, decides more about a site’s long-term maintainability than the choice of theme does.
A site-specific plugin is one file with a header comment, dropped into wp-content/plugins/:
<?php
/**
* Plugin Name: Example Site Tweaks
* Description: Small customisations specific to this site.
* Version: 1.0.0
*/
add_action( 'after_setup_theme', function () {
add_image_size( 'card-thumb', 600, 400, true );
} );
That example is deliberate. Registering a custom image size is one of the most common reasons people are told to build a child theme, and it is one of the worst fits for one. The size only applies to images uploaded after it is registered, so if it stops being registered because you changed themes, every file it already generated stays on disk while nothing on the site asks for it any more. Put it in a plugin and it keeps working through a redesign.
Where CSS belongs
If your customisation is only CSS, you need no child theme at all. Additional CSS in the Customizer stores your rules in the database and WordPress prints them late in the <head>, after the enqueued stylesheets, so rules of equal specificity win on source order. It survives parent theme updates for the same reason a child theme does: it is not in the theme’s files. Block themes have the same field under Styles in the Site Editor, and plenty of commercial themes ship their own equivalent in their options panel.
The one real limitation is that Additional CSS is stored per theme, so it does not follow you when you switch. Neither does a child theme’s style.css. On that axis they are equivalent, and the box that needs no files, no FTP and no enqueue snippet wins.
The cost of a frozen copy
An overridden template file is a frozen copy. That sounds like the point, because it is why you made it, but consider what freezing means over time.
Say the parent theme ships an update that fixes an escaping bug in content-single.php, changes the markup so a new block style applies, and adds a do_action() hook that three of the theme’s own add-ons rely on. Your copy of that file receives none of it. Nothing in the admin tells you. The Themes screen shows the parent as up to date, because it is. You are simply no longer using the file that was updated.
Override five templates and you have five files to diff against the parent every time the parent ships a major release. Very few tools help. WooCommerce is the notable exception: its template files carry a version number in the file header, and its System Status report lists overridden templates that have fallen behind the version the plugin expects. Core and almost every theme offer nothing equivalent. You are the version check.
This is the argument for overriding as little as possible. Before copying a template, look for a hook, a filter or a theme setting that gets you the same result. A three-line filter in a plugin costs nothing at update time. A copied 200-line template costs a review, forever.
Block themes change the arithmetic
With a block theme, editing a template in the Site Editor does not touch a file. WordPress saves your version as a wp_template post in the database, and from then on the database copy takes priority over the theme’s own templates/*.html file. You get template customisation that survives updates without creating a child theme at all.
The trap moved rather than disappeared. That database copy freezes in exactly the same way a child theme override does, because updates to the underlying file stop reaching your site. The difference is that the Site Editor gives you a way out: a customised template offers an action that discards the database copy and returns you to the theme’s current file. There is no equivalent button for a copied PHP template.
Child themes still have a place with block themes, mostly when you want customisations in files you can version-control and deploy rather than in a database you have to migrate. There is also one genuinely useful piece of asymmetry: a child theme’s theme.json is merged into the parent’s rather than replacing it, with the child’s values taking precedence, so you can override a few palette colours or spacing values without restating the parent’s entire configuration. That is closer to how functions.php behaves than to how templates behave.
Which option fits
- CSS only, no PHP: Additional CSS in the Customizer, or the theme’s own custom CSS field. No child theme.
- A handful of PHP snippets: a site-specific plugin. It survives theme switches and can be deactivated when it breaks.
- Template overrides on a theme that receives updates: a child theme. This is what child themes are for.
- A block theme where you only need layout changes: edit in the Site Editor, and remember the result lives in the database.
- A theme you built yourself that nobody else updates: no child theme. Edit the theme. There is no upstream to protect against.
Most real sites are a mix, and the answer there is both: a child theme for the templates, a plugin for the snippets. Splitting them is not overkill, it is what makes the next redesign survivable.
When something looks wrong
The child theme activates but the site is completely unstyled. The parent stylesheet is not being loaded. Either the child’s functions.php has no enqueue for it, or the enqueue used get_stylesheet_directory_uri(), which points at the child, where it needed the parent’s URI.
WordPress says the parent theme is missing. The Template line in the child’s style.css does not match the parent’s folder name. It is the directory name, not the display name, and on most servers it is case-sensitive.
A fatal “cannot redeclare function” error after editing functions.php. The child’s functions.php loads before the parent’s, so a function you defined collides when the parent file declares the same name. Either the parent wraps that function in function_exists() and you spelled the name wrong, or it does not and the function was never meant to be overridden.
The copied template file has no effect. The path in the child does not mirror the path in the parent, or the parent loads that file with a hardcoded get_template_directory() include rather than through the standard lookup. Only files reached through the template hierarchy or functions like locate_template() can be overridden.
Customisations disappeared after a theme update. The edits were made directly in the parent theme’s files, not in a child. There is no recovery except a backup, which is the actual reason child themes exist and the only symptom on this list they prevent.
Where this leaves you
A child theme is not a safety measure you apply to a site in general. It is a targeted answer to one question: how do I change a file that somebody else is going to overwrite? If nobody is going to overwrite the file, or if you are not changing a file at all, the question does not apply and the child theme is structure without purpose.
The most useful reframing is to stop thinking of it as protection and start thinking of it as a fork. Every template you copy into a child theme is a small fork of somebody else’s code, and forks have upkeep. That is a reasonable trade for a change you genuinely need and cannot get from a hook. It is a bad trade for a change you could have made with six lines of CSS.
If you already have a near-empty child theme, you do not have to tear it down. It is not hurting much beyond an extra request. But when the next customisation comes up, ask what it actually is before deciding where it goes. CSS to the CSS box, snippets to a plugin, templates to the child theme. Sorted that way, each piece ends up somewhere it can survive the thing most likely to break it.