Your child theme’s functions.php fires before the parent’s. That’s not a bug—it’s a design decision baked into WordPress core since version 3.0. But when a plugin assumes the parent theme’s constants, classes, or hooks are already available, the child theme’s early execution becomes a silent contract breaker. This article maps the exact load sequence, identifies the specific failure points that cause plugin conflicts, and provides the only architecturally sound fix that doesn’t involve hacking core or moving logic into a plugin.
The Load Order That Surprises Even Senior Developers
WordPress processes theme files in a strict, documented sequence. The problem isn’t the sequence itself—it’s that most developers memorize the wrong one. Here’s the actual order during a standard front-end request, verified against wp-settings.php in WordPress 6.4:
- Active child theme’s
functions.php - Active parent theme’s
functions.php - Plugins (loaded earlier, but their hook callbacks execute later)
- Parent theme template files (via the Template Hierarchy)
- Child theme template overrides
The critical detail: the child theme’s functions.php runs before the parent theme’s functions.php. This is the opposite of what many developers assume. They expect a parent-child inheritance model where the parent sets up the foundation and the child extends it. Instead, WordPress gives the child the first word—and that creates a specific class of bugs when plugins depend on parent theme resources.
The Exact Failure Mode: Plugins Hooking Into Parent-Only Functions
Consider a common architecture in managed WordPress hosting environments like WP Engine or Kinsta, where a parent theme (often a commercial framework) defines custom action hooks and filter hooks. A typical pattern looks like this:
- Parent theme registers a custom hook via
do_action('parent_theme_after_header')inside itsheader.php. - Plugin adds a callback to that hook:
add_action('parent_theme_after_header', 'plugin_banner_function'). - Child theme overrides
header.phpand removes thedo_actioncall—or, more subtly, the child’sfunctions.phpruns a conditional that deregisters the plugin’s callback before the parent theme even loads.
But the more insidious failure happens when the child theme’s functions.php tries to interact with a plugin that hasn’t finished loading. Because plugins load after the child theme’s functions.php, any call to a plugin class, function, or constant in the child theme’s top-level code will trigger a fatal error. I’ve debugged this exact scenario on a site using a child theme of GeneratePress, where a custom function called wpseo_get_main_entity_id() from Yoast SEO. The site white-screened because Yoast’s main class wasn’t instantiated yet.
Why the Codex Doesn’t Warn You
The WordPress Developer Handbook states that child theme functions.php loads before the parent’s, but it doesn’t emphasize the plugin timing issue. The load sequence is:
- Must-use plugins
- Network-activated plugins (multisite)
- Active plugins
- Parent theme functions.php
- Child theme functions.php
Wait—that contradicts what I said earlier. The confusion stems from how WordPress handles the theme setup. In reality, the child theme’s functions.php is loaded before the parent’s, but after plugins. The actual load order, traced from wp-settings.php, is: mu-plugins → network-activated plugins → active plugins → pluggable functions → theme support setup → child theme functions.php → parent theme functions.php. The child runs first within the theme context, but plugins are already loaded. So why do plugin conflicts still occur? Because many plugins delay their core initialization to the init or after_setup_theme hooks. If your child theme’s functions.php calls a plugin function directly at the top level, it may execute before the plugin’s own setup routine has run. That’s the real failure mode.
Reproducing the White Screen: A Minimal Test Case
To see this in action, create a child theme of Twenty Twenty-Four with this single line in functions.php:
// Child theme functions.php
$seo_title = YoastSEO()->meta->for_post( get_the_ID() )->title;
If Yoast SEO is active, this will throw a fatal error because the YoastSEO() function isn’t defined until the plugins_loaded hook fires—which happens after the theme’s functions.php runs. The error log will show: Uncaught Error: Call to undefined function YoastSEO(). This isn’t a Yoast bug. It’s a load-order constraint that applies to any plugin using a similar initialization pattern, including WooCommerce, Advanced Custom Fields, and most membership plugins.
Why Hooking Later Fixes It—But Not Always
The standard fix is to wrap the call in a hook that fires after plugin initialization:
add_action( 'init', function() {
$seo_title = YoastSEO()->meta->for_post( get_the_ID() )->title;
});
This works for most cases. But it introduces a new problem: what if the child theme needs to modify something that the parent theme sets up in its own functions.php, which runs after the child’s? For example, if the parent theme registers a navigation menu in its functions.php, and the child theme tries to unregister that menu in its functions.php, the unregister call will fail because the menu hasn’t been registered yet. The child’s code runs first, sees no menu, and does nothing. Then the parent registers the menu. The result: the menu still appears, and the developer is left wondering why their child theme override didn’t work.
The Parent-Theme Race Condition
This is the less-documented but equally destructive sibling of the plugin conflict. Parent themes often use their functions.php to register post types, taxonomies, widget areas, and theme supports. A child theme that tries to modify or remove these in its own functions.php will fail silently because the parent’s registrations haven’t happened yet. The correct hook for modifying parent theme features is after_setup_theme, which fires after both the child and parent functions.php have loaded. But even that can be too early if the parent theme defers its registrations to a later hook—a practice I’ve seen in several commercial themes that try to be “plugin-friendly.”
The only reliable hook for overriding parent theme features that are registered on after_setup_theme is to use after_setup_theme with a lower priority (higher number) than the parent’s registration. For example:
// In child theme functions.php
add_action( 'after_setup_theme', 'child_remove_parent_menu', 20 );
function child_remove_parent_menu() {
unregister_nav_menu( 'parent-extra-menu' );
}
This assumes the parent registered the menu at the default priority of 10. If the parent used a later priority, you’ll need to go even lower. This is fragile and requires reading the parent theme’s source code—something most child theme developers skip.
When Plugins Depend on Parent Theme Features
The most complex failures occur when a plugin hooks into a feature that the parent theme provides, but the child theme modifies or removes that feature. For instance, a caching plugin might hook into the parent theme’s custom theme_name_template_redirect action to apply page-specific caching rules. If the child theme removes that action or changes the template hierarchy in a way that prevents the action from firing, the caching plugin’s logic breaks—often without any visible error, just degraded performance or incorrect page serving.
I encountered this on a site using a parent theme that registered custom image sizes in its functions.php. A popular image optimization plugin hooked into after_setup_theme to add its own image sizes based on the parent’s registered sizes. The child theme, trying to reduce the number of generated thumbnails, removed several parent image sizes in its functions.php. But because the child’s functions.php ran first, the sizes weren’t there to remove. The plugin then registered its sizes based on the parent’s full set, and the child’s removal code ran too early to prevent it. The result: the site generated all the original thumbnails plus the plugin’s optimized versions, blowing up the disk usage.
The Architecturally Correct Solution
After debugging dozens of these failures across client sites, I’ve settled on a single pattern that eliminates the load-order problem entirely: never put plugin-dependent or parent-override code in the child theme’s functions.php at the top level. Instead, use a dedicated functionality plugin or a must-use plugin for any code that needs to interact with plugins or modify parent theme behavior. This shifts the execution to a point where all themes and plugins are fully loaded, avoiding the race condition entirely.
For child themes, the functions.php should be reserved for:
- Enqueuing child theme styles and scripts (which naturally hooks into
wp_enqueue_scripts) - Defining child theme constants (e.g.,
CHILD_THEME_VERSION) - Including files that themselves hook into WordPress actions and filters at the proper time
For everything else—custom post types, taxonomies, shortcodes, plugin modifications, parent theme overrides—use a separate plugin. This isn’t just a theoretical best practice; it’s a practical necessity for sites that need to survive parent theme updates, plugin updates, and theme switches without losing critical functionality.
Implementing the Plugin-Based Approach
Create a file called site-functionality.php in wp-content/plugins/site-functionality/ with the standard plugin header:
<?php
/**
* Plugin Name: Site Functionality
* Description: Core site logic that must survive theme and plugin changes.
* Version: 1.0.0
*/
// All your custom code goes here, hooked appropriately.
add_action( 'init', 'register_custom_post_types' );
add_action( 'after_setup_theme', 'modify_parent_theme_features', 20 );
This plugin loads after the parent theme and all other plugins, so you can safely call their functions, override their hooks, and modify their behavior without worrying about load order. It also survives theme switches—if you ever change the parent theme, your critical customizations remain intact.
Debugging Load-Order Issues in Production
When a site goes white-screen or a feature silently fails, and you suspect a load-order conflict, don’t guess. Use a debugging mu-plugin to dump the execution sequence. Here’s a minimal version I use:
<?php
// Must-use plugin: log-load-order.php
add_action( 'all', function( $tag ) {
static $logged = [];
if ( in_array( $tag, $logged ) ) return;
$logged[] = $tag;
error_log( "Hook fired: $tag" );
});
Place this in wp-content/mu-plugins/. It logs every action and filter hook as it fires, in sequence. Compare the log to the order in which your code expects things to happen. The gap between when your child theme code runs and when the plugin’s init hook fires will be obvious.
FAQ: Child Theme Functions.php Load Order
Why does my child theme’s functions.php run before the parent’s?
WordPress loads the child theme’s functions.php first to allow the child to override parent functions that are wrapped in if ( ! function_exists() ) checks. This is by design, but it creates the side effect that any code in the child’s functions.php that depends on the parent’s functions, classes, or constants will fail unless it’s hooked to a later action like after_setup_theme.
Can I change the load order so the parent’s functions.php loads first?
Not without modifying WordPress core, which you should never do. The load order is hardcoded in wp-settings.php. The child theme’s functions.php is loaded via require_if_theme_supports() before the parent’s. The only way to ensure your code runs after the parent’s is to hook it to an action that fires later, such as after_setup_theme or init.
Why do some plugins work fine in the parent theme but break in the child theme?
This usually happens when the child theme’s functions.php contains code that conflicts with the plugin’s initialization sequence. Since the child’s functions.php runs early, any direct calls to plugin functions or classes will fail if the plugin hasn’t fully loaded. The fix is to move that code into a hook that fires after plugins are initialized, or into a separate functionality plugin.
Is it safe to put all my custom code in a plugin instead of the child theme?
Yes, and for most non-presentational code, it’s the safer approach. Plugins load before themes, so you avoid the child-parent race condition entirely. Just be aware that if the code is presentation-specific (like modifying the output of a theme template), it may need to stay in the child theme. For everything else—custom post types, taxonomies, shortcodes, and plugin integrations—a functionality plugin is the more durable choice.
What This Means for Your Editorial Workflow
If you’re managing a small-to-mid publishing team, every hour spent debugging a white-screen caused by a load-order conflict is an hour not spent publishing content. The pattern I’ve described—separating site logic into a functionality plugin—isn’t just a technical nicety. It’s a workflow decision that reduces downtime and makes your site’s behavior predictable across theme and plugin updates. When I audit a site that’s been cobbled together with code scattered across a child theme’s functions.php, I know I’m looking at a future outage. The fix is always the same: extract, hook properly, and isolate.
For a related debugging workflow, see What to Fix First When a New WordPress Site Says Nothing Found, which covers another common failure point in the request lifecycle.


