Fixing Too Few Arguments to Function in a WordPress Hook
When the error says the arguments were passed in class-wp-hook.php, WordPress called your function with fewer values than it declares. The cause is nearly always the fourth parameter of add_action or add_filter, which defaults to one.
WordPress called your hook callback with fewer arguments than the function requires, almost always because add_action() or add_filter() was left at its default of one. Fatal error: Uncaught ArgumentCountError: Too few arguments to function my_save_handler(), 1 passed in /wp-includes/class-wp-hook.php on line 371 and exactly 3 expected names your function first, then the place it was called from. The second path is WordPress's hook runner, not the bug. The bug is in the line where you attached the function.
Why WordPress passed only one
Both registration functions take four parameters, and the last one is how many arguments to hand over:
add_action( $hook_name, $callback, $priority = 10, $accepted_args = 1 );
add_filter( $hook_name, $callback, $priority = 10, $accepted_args = 1 );
When the hook fires, WP_Hook::apply_filters() compares that number with what the call site provided:
0— the callback is called with no arguments.- equal to or more than the number provided — the callback receives all of them.
- fewer — WordPress slices the list with
array_slice()and passes only the first$accepted_args.
So this is a fatal error on every post save:
add_action( 'save_post', 'my_save_handler' );
function my_save_handler( $post_id, $post, $update ) {
// ...
}
save_post provides three values — post ID, post object and an update flag — but registration asked for one. PHP 7.1 and later throw ArgumentCountError when a user-defined function receives fewer arguments than it has required parameters, and an uncaught one is a fatal error. The fix is the fourth argument:
add_action( 'save_post', 'my_save_handler', 10, 3 );
Priority has to be passed to reach the fourth position; 10 is the default, so writing it changes nothing else. The save_post and wp_insert_post_data guides list what each of those hooks passes.
Finding the right number
The number to use is how many values the call site passes, which is in the do_action() or apply_filters() line, not in the documentation you copied from. For core hooks, search the source:
grep -rn "do_action( 'save_post'" wp-includes/
grep -rn "apply_filters( 'the_title'" wp-includes/
Count the arguments after the hook name. For a filter, the first one is the value being filtered and the rest are context. For plugin hooks, grep the plugin's folder the same way. The callback may declare fewer parameters than the hook passes — PHP ignores extra arguments to user functions — but never more required parameters than $accepted_args.
The hook that is fired from more than one place
This is the case that makes the error appear on one page only. A hook name is just a string, and nothing stops a theme or plugin from calling it with fewer arguments than core does. Core passes two values to the_title — the title and the post ID — in every place it applies the filter. A theme that runs apply_filters( 'the_title', $heading ) on a custom heading passes one. A callback written as:
add_filter( 'the_title', 'my_title_prefix', 10, 2 );
function my_title_prefix( $title, $post_id ) {
return get_post_type( $post_id ) === 'event' ? 'Event: ' . $title : $title;
}
works everywhere core is the caller and fails on the one template that calls the filter itself. The robust signature gives every non-first parameter a default:
function my_title_prefix( $title, $post_id = 0 ) {
if ( ! $post_id ) {
return $title;
}
return get_post_type( $post_id ) === 'event' ? 'Event: ' . $title : $title;
}
Treat $accepted_args as the maximum you can use and the defaults as protection against call sites you do not control. The same discipline applies to the_content, pre_get_posts and any hook that third-party code also fires.
When the call does not come from a hook
If the second path in the message is a plugin or theme file rather than class-wp-hook.php, a function was called directly with too few arguments. Two patterns are common:
- A plugin update changed a function's signature. Your theme calls
some_plugin_helper( $id ), the new version requires( $id, $context ), and every page using it fails. Check the plugin's changelog and pass the new argument. - A core function called from a snippet without a required parameter. The message names the core function; the file in the
passed inpart is where your snippet sits.
The stack trace in the log, below the message, lists each call in order and shows which one is yours. If the log is empty, switch logging on as described in debug mode.
Getting the site back
An ArgumentCountError on init, wp_loaded or save_post usually takes down wp-admin as well. If you see "There has been a critical error on this website", the recovery mode email from critical error pauses the plugin or theme responsible. Without the email, rename the plugin's folder over SFTP or, for a snippet in functions.php, remove the add_action() line, fix it, and put it back.
Common mistakes
- Raising the priority instead of
$accepted_args. The third parameter is when the callback runs, not what it receives. - Passing
$accepted_argsas the third argument:add_action( 'save_post', 'fn', 3 )sets priority 3 and still passes one argument. - Copying a callback from a class method into a closure and forgetting the registration still says
1. - Changing the priority while fixing the registration and leaving a
remove_action()elsewhere on the old one. Removal must match the callback and priority exactly, or the callback stays attached.
Verify
- Trigger the hook directly: save a post, load the template, run the cron event.
wp-content/debug.logshows no newArgumentCountErrorfor the function.- For filters fired by other code, test the page or screen where the error first appeared, not only a standard post.
grepyour code foradd_actionandadd_filterlines whose callbacks take more than one parameter and check each has a fourth argument.
Frequently asked
- Because that is where the call happens. WP_Hook::apply_filters() calls every callback with call_user_func_array(), so PHP reports the caller's location. The function named at the start of the message is yours; that is the one to fix.
- It works, because WordPress passes all available arguments when accepted_args is at least the number provided. It also hides how many the hook really passes and does nothing for a call site that passes fewer. Use the real number.
- The same hook can be fired from several places with different numbers of arguments. Core passes two values to the_title, for example, but a theme or plugin may call apply_filters('the_title', $text) with one. Give optional parameters a default value.