Function … Was Called Incorrectly: What the WordPress Notice Means
The notice names the WordPress function that noticed the mistake, not the plugin that made it, and its file and line always point into wp-includes. Here is how to read it, find the real caller and fix the common ones, including translation loading triggered too early.
"Function X was called incorrectly" is a WordPress developer notice: some plugin or theme used a core function at the wrong time or with the wrong arguments, and the code still ran. A typical log line reads PHP Notice: Function _load_textdomain_just_in_time was called <strong>incorrectly</strong>. Translation loading for the <code>my-plugin</code> domain was triggered too early. … (This message was added in version 6.7.0.) in /var/www/html/wp-includes/functions.php on line 6368. The HTML tags are literal; WordPress builds the message for screen display and the log keeps the markup.
What the notice is made of
Core calls _doing_it_wrong( $function_name, $message, $version ) whenever it detects misuse. The function only produces output when WP_DEBUG is true, and passes the text to wp_trigger_error(), which raises it as E_USER_NOTICE. The parts:
| Part | What it tells you |
|---|---|
Function X |
The core function that detected the problem, not the code that caused it |
| Middle sentence | What was wrong and what to do instead |
(This message was added in version N) |
When core started checking, which is why an update makes it appear "out of nowhere" |
in …/wp-includes/functions.php on line … |
Where trigger_error() lives. Useless for finding the culprit |
That last row is the trap: every one of these notices points to the same core file. Searching that file, or reinstalling WordPress because of it, gets you nowhere.
Find the real caller
The notice fires through the doing_it_wrong_run action before the message is built, so you can log a call stack with it. Save this as wp-content/mu-plugins/doing-it-wrong-trace.php while you debug, with debug logging enabled:
<?php
add_action( 'doing_it_wrong_run', function ( $function_name ) {
error_log( 'doing_it_wrong: ' . $function_name . ' <- ' . wp_debug_backtrace_summary() );
} );
wp_debug_backtrace_summary() returns the chain of calls as one line, outermost first. Read it from the right-hand end backwards and skip the core functions: the first function, class method or require_once('/plugins/…') / ('/themes/…') entry that belongs to a plugin or theme is the code to fix or report. The summary shortens paths relative to wp-content, and function frames carry no path at all, so search the plugin folders for that name. Delete the file when you are done — it writes a line for every notice on every request.
No log access? Deactivate plugins one at a time and watch the notice disappear; the usual conflict-testing method works, just slower.
The common messages
| Function in the notice | Message (shortened) | Cause |
|---|---|---|
_load_textdomain_just_in_time |
Translation loading for the … domain was triggered too early | Translated strings requested before themes are set up |
wp_enqueue_script, wp_register_style and relatives |
Scripts and styles should not be registered or enqueued until the wp_enqueue_scripts, admin_enqueue_scripts, or login_enqueue_scripts hooks |
Enqueue called at plugin load, before init |
wpdb::prepare |
The query argument of wpdb::prepare() must have a placeholder | prepare() used on a query with no %s/%d |
wpdb::prepare |
The query does not contain the correct number of placeholders | Argument count does not match placeholders |
register_rest_route |
The REST API route definition for … is missing the required permission_callback argument |
Route registered without a permission check |
The scripts fix is to hook into wp_enqueue_scripts or admin_enqueue_scripts instead of calling enqueue functions in the plugin file body. For REST routes, public endpoints should pass 'permission_callback' => '__return_true' explicitly, and everything else a real capability check. The prepare() notices mean the query is either pointless to prepare or will not be escaped the way the developer thinks; both are fixed in the calling code.
Translation loading was triggered too early
This one arrived with WordPress 6.7 and is one of the most common. WordPress loads a plugin's or theme's translations lazily, the first time one of its strings is requested. Since 6.7, if that first request happens before the after_setup_theme action has started, core raises the notice. It only fires when a translation file exists for the current language, so a site running in plain English may never show it.
Plugins are included before after_setup_theme, so any of these trigger it:
__()oresc_html__()with the plugin's text domain in the main plugin file, outside any function;- a class constructor that builds translated labels and is instantiated when the file loads or on
plugins_loaded; - translated strings on
plugins_loaded, even right afterload_plugin_textdomain(): since 6.7 that function only registers the path, and the first string still triggers loading.
The message says translations "should be loaded at the init action or later", while the check itself only looks at after_setup_theme. Moving code to after_setup_theme silences the notice, but init is the right hook: the current user is set up immediately before init. Strings translated while plugin files are still loading come out in the site language instead of the user's profile language, because the user functions in pluggable.php do not exist yet.
// Before: labels built when the plugin file loads.
$labels = array( 'name' => __( 'Bookings', 'my-plugin' ) );
// After: built when they are needed.
add_action( 'init', function () {
register_post_type( 'booking', array(
'labels' => array( 'name' => __( 'Bookings', 'my-plugin' ) ),
) );
} );
If it is not your code, update the plugin and check its changelog for the text domain fix; if none exists yet, report it with the backtrace line from the trace above. Until then the notice is harmless on a site with WP_DEBUG off.
Common mistakes
- Debugging
wp-includes/functions.phpbecause the notice names it. - Leaving
WP_DEBUG_DISPLAYon in production. Printed notices before page output cause headers already sent warnings and break JSON responses. - Silencing every notice with
add_filter( 'doing_it_wrong_trigger_error', '__return_false' ), which also hides the next, more useful one. Filter by the$function_nameargument instead. - Treating it as the cause of a white screen. A notice never stops the page; look for a fatal error nearby, as in WordPress critical error.
Verify
- After the fix, load the page that produced the notice and check that
debug.loghas no new "called incorrectly" line for that function. - With the trace plugin still in place, the
doing_it_wrong:lines for that function stop appearing. - For translation fixes, switch your profile language in Users › Profile and confirm the plugin's admin labels follow it.
Frequently asked
- No. It is a developer notice raised at E_USER_NOTICE level, and WordPress only raises it when WP_DEBUG is true. The code still runs. It can break things indirectly when notices are displayed on screen: printed before headers, they cause "headers already sent" warnings and can corrupt AJAX or JSON responses.
- Because that is where WordPress calls trigger_error(). The file and line describe where the notice was raised, not where the mistake was made. The function name in the message is the WordPress function that detected the mistake; the plugin or theme that called it is further up the call stack.
- Yes: turn WP_DEBUG off on production, or return false from the doing_it_wrong_trigger_error filter for one specific function name. Hiding it does not change behaviour. For translation loading triggered too early, the strings loaded that early may still appear untranslated or in the wrong language, so the fix belongs in the plugin.