You copy a messy function from your parent theme into your child theme’s functions.php, tweak a line, refresh the page, and get slapped with a white screen: Fatal error: Cannot redeclare parent_theme_function(). The original theme author didn’t wrap their function in function_exists(), so PHP refuses to let you redefine it. Here are four ways I work around unpluggable functions without ever touching parent theme files.

The Child Theme Execution Order Trap
A lot of developers assume WordPress loads the parent theme first and lets the child theme overwrite it down the chain. It’s actually the opposite: WordPress runs your child theme’s functions.php before the parent’s.
When a theme author writes proper pluggable code, they leave the door open with a safety check:
// Inside parent theme functions.php
if ( ! function_exists( 'parent_render_breadcrumbs' ) ) { function parent_render_breadcrumbs() { echo ''; }
}Because your child theme loads first, your version of parent_render_breadcrumbs() claims the namespace. When the parent theme file loads a fraction of a second later, its function_exists() check sees yours, returns true, and gracefully steps aside.
An unpluggable function skips that check completely. If the parent declares a bare function, you can’t redeclare it anywhere in PHP without throwing a fatal error. And editing the parent theme directly isn’t an option unless you enjoy re-applying patches every time an update wipes them out.
Step 1: Inspect How the Parent Theme Runs the Function
Before writing any child theme code, run a global search in your parent theme directory for the offending function’s name. You need to see how WordPress actually calls it.
In almost every case, you’ll find one of two patterns:
- Hooked Execution: The function registers through
add_action()oradd_filter(). - Direct/Procedural Execution: The function is called directly inside a template file like
header.phporsingle.php.
If you’re dealing with a bloated commercial setup, it’s also worth taking a minute to audit your WordPress theme to see if the developers already provided undocumented filters or alternate child-theme hooks.
Method 1: Unhook the Parent Action and Register Your Own
Most parent theme functions you want to change aren’t floating in isolation—they’re attached to WordPress action hooks. When that’s the case, you don’t need to overwrite the PHP function itself. You just tell WordPress not to run it using remove_action().
Say the parent theme hooks output like this:
// In parent theme/inc/layout.php (no function_exists check)
function parent_render_footer_credits() { echo 'Powered by BadParentTheme
';
}
add_action( 'wp_footer', 'parent_render_footer_credits', 20 );In your child theme, wait until the parent has finished setting up its hooks, unhook that callback, and hook your replacement in its place:
// In child theme/functions.php
function child_replace_footer_credits() { // 1. Remove the parent function remove_action( 'wp_footer', 'parent_render_footer_credits', 20 ); // 2. Add your custom child implementation add_action( 'wp_footer', 'child_render_custom_footer_credits', 20 );
}
add_action( 'after_setup_theme', 'child_replace_footer_credits' ); function child_render_custom_footer_credits() { echo '© ' . date('Y') . ' My Custom Company Name
';
}I use this exact same pattern when cleaning up bloated assets. For example, you can dequeue unused scripts and styles during the enqueue hook using the exact same priority logic.
Method 2: Unhook Functions Inside Classes and Singletons
Commercial themes love wrapping everything in classes. A plain remove_action( 'wp_head', 'output_tracking_code' ) won’t touch a method that belongs to a class instance—it’ll just fail silently.
Static Class Methods
If the method is static, you can target it by passing an array containing the class name as a string alongside the method name:
function child_remove_parent_static_hook() { remove_action( 'wp_head', array( 'Parent_Theme_SEO', 'output_tracking_code' ), 15 );
}
add_action( 'init', 'child_remove_parent_static_hook' );Singleton or Instantiated Objects
When the theme instantiates a singleton or stashes the object in a global variable, pass that exact instance into the removal call:
function child_remove_singleton_hook() { if (class_exists('Parent_Theme_Core' ) && method_exists('Parent_Theme_Core', 'get_instance' ) ) { $instance = Parent_Theme_Core::get_instance(); remove_action('wp_body_open', array($instance, 'inject_announcement_bar' ), 10 ); }
}
add_action('after_setup_theme', 'child_remove_singleton_hook' );Anonymous Class Instances in $wp_filter
Sometimes theme authors instantiate classes directly into hooks—like add_action( ‘init’, [ new Parent_Hero_Widget(), ‘render’ ] )—without saving the instance to a variable. To disarm those, you have to dig into the global $wp_filter array directly:
function child_unhook_anonymous_class_method($tag, $class_name, $method_name, $priority = 10 ) { global $wp_filter; if (! isset($wp_filter[ $tag ]->callbacks[ $priority ] ) ) { return false; } foreach ($wp_filter[ $tag ]->callbacks[ $priority ] as $callback_id => $callback ) { if (is_array($callback['function'] ) && is_object($callback['function'][0] ) ) { if (get_class($callback['function'][0] ) === $class_name && $callback['function'][1] === $method_name ) { return remove_action($tag, $callback_id, $priority ); } } } return false;
}
// Usage:
add_action('init', function() { child_unhook_anonymous_class_method('wp_footer', 'Parent_Banner_Display', 'render_popup', 25 );
}, 100 );This iterates over every callback registered to that hook, identifies the object built from your target class, and pulls it off the stack cleanly.
Method 3: Intercept Hardcoded Calls via Template Overrides
What if the parent theme doesn’t use hooks at all? You’ll often see this in older or procedural themes where templates are full of hardcoded function calls:
// Inside parent-theme/header.php
Because that function call is baked right into the template file, remove_action() won’t do anything. The fix here is straightforward: override the template file itself.
- Copy the parent template file (like
parent-theme/header.php) into your child theme directory:child-theme/header.php. - Delete or swap out the call to
parent_render_hero_slider()right in your child theme’s copy. - Write your own function in
child-theme/functions.phpwith a distinct name (likechild_render_hero_slider()) and call that instead.
WordPress checks the child theme directory first; if it finds header.php there, it never touches the parent’s copy. If your edits don’t show up immediately, double-check your cache setup—I’ve spent hours debugging template changes only to realize I needed to configure Redis Object Cache in WordPress properly so the object and opcode caches stopped serving stale file paths.
Method 4: The Output Buffering Hijack
Then there’s the worst-case scenario: an unpluggable function is hardcoded inside an included file that WordPress’s template hierarchy can’t override, and it echoes markup straight to the browser without any filters.
If you can’t intercept the logic, catch the output instead using PHP’s output buffer. Open a buffer right before the function fires, let it dump its markup, and clear the buffer before it reaches the browser:
// Start capturing output right before the parent hook executes
add_action( 'parent_before_sidebar_hook', function() { ob_start();
}, 0 ); // Collect buffer, discard the parent output, and inject child HTML
add_action( 'parent_after_sidebar_hook', function() { $parent_html = ob_get_clean(); // Discards the unpluggable output // Render your child content instead echo '';
}, 999 );It’s definitely an escape hatch, but it beats forking a 3,000-line file just to silence 10 lines of unwanted HTML.
Debugging: Why remove_action() Fails to Disarm the Function
If you hooked up remove_action() and the parent code is still running, you hit one of three classic gotchas. Here’s how to track it down.
1. The Priority Must Match Exactly
If the parent theme registered its function with add_action( ‘wp_head’, ‘parent_code’, 15 ) and you run remove_action( ‘wp_head’, ‘parent_code’ ) without passing 15, nothing happens. WordPress defaults to priority 10. If the priority doesn’t match the original registration down to the integer, the removal returns false.
2. Timing: You Removed the Hook Before It Was Registered
Remember execution order: your child theme’s functions.php runs first. If you call remove_action() in the global scope of your child theme file, you’re trying to remove an action that the parent hasn’t even registered yet.
Always wrap your removal logic inside a hook that fires after the parent theme has initialized, like after_setup_theme or init:
// WRONG: Fires before parent theme registers the hook
remove_action( 'wp_enqueue_scripts', 'parent_scripts', 10 ); // RIGHT: Waits until parent theme has registered its hooks
add_action( 'after_setup_theme', function() { remove_action( 'wp_enqueue_scripts', 'parent_scripts', 10 );
}, 20 );3. Verifying Hook Registration with has_action()
Instead of guessing whether your removal code is running at the right time, verify what WordPress sees in memory:
add_action( 'wp_loaded', function() { $priority = has_action( 'wp_footer', 'parent_render_footer_credits' ); if ( false !== $priority ) { error_log( "Found parent function at priority: " . $priority ); } else { error_log( "Parent function is not registered on this hook." ); }
});Check your wp-content/debug.log file. If it prints a number, that’s the exact priority you need to feed into remove_action(). If it prints false, your check is firing too early or the callback name is wrong.
Frequently Asked Questions
Can I use PHP uopz or runkit to redefine an unpluggable function?
While extensions like uopz_redefine_function() or runkit7 work in local testing or CLI environments, they rely on custom PHP extensions that almost no managed WordPress host will allow. Stick to hook removal and template overrides—they work across every environment without custom server modules.
Will parent theme updates break my remove_action workarounds?
Only if the theme author changes the function name, the hook it attaches to, or its priority. The good news: even if an update shifts things around, your site won’t fatal error. The old parent output might temporarily reappear until you update the hook arguments, but your site stays online.
What should I do if an unpluggable function returns a value inside an apply_filters call?
If the function passes its output through a filter (like apply_filters( ‘parent_filter_name’, $output )), you don’t need to touch the function at all. Just attach to that filter with a late priority (like 99) and overwrite the return value using remove_filter() or add_filter().
Once you’ve got your child theme overrides squared away, check out our guide on how to fix the link you followed has expired error in WordPress if you run into upload size limits while packaging custom themes.

