WP_Query used internally in get_pages()

In WordPress 6.3, the function get_pages() has been updated to utilize WP_Query internally, resolving a 13-year-old ticketticket Created for both bug reports and feature development on the bug tracker. (#12821). This modification significantly reduces the complexity of the get_pages() function by offloading the burden of querying databases and handling the cache to WP_Query. The change builds upon the previous enhancementenhancement Enhancements are simple improvements to WordPress, such as the addition of a hook, a new feature, or an improvement to an existing feature. introduced in #22176, which introduced query caching to WP_Query.

As a result, this update eliminates redundant code and ensures that all filters running in WP_Query are now also applied during the call to get_pages(). Users who leverage filters like posts_pre_query or posts_results to customize the behavior of WP_Query, such as retrieving data from alternative sources like cache or another database (e.g., elastic search), will benefit from this change.

Additionally, based on feedback from the glotpress team, a new filterFilter Filters are one of the two types of Hooks https://codex.wordpress.org/Plugin_API/Hooks. They provide a way for functions to modify data of other functions. They are the counterpart to Actions. Unlike Actions, filters are meant to work in an isolated manner, and should never have side effects such as affecting global variables and output. named get_pages_query_args has been added. This filter enables developers to modify the parameters passed to WP_Query, maintaining compatibility with the original parameter arguments.

See #56586 and #55806 for additional context.

Props to @flixos90 for peer review, to @stevenlinx and @leonnugraha for review.

#6-3, #dev-notes, #dev-notes6-3

Improved Caching for Database Queries in WP_User_Query

In WordPress 6.3, significant enhancements have been made to the WP_User_Query class, specifically regarding caching of database queries. This update builds upon the ongoing efforts of the performance team to optimize query caching for various WordPress CoreCore Core is the set of software required to run WordPress. The Core Development Team builds WordPress. query classes. WP_Query introduced query caching in WordPress 6.1, while other query classes like WP_Comment_Query, WP_Site_Query, WP_Network_Query, and WP_Term_Query already had built-in query caching. As a result, WP_User_Query was the only remaining query class lacking this caching capability.

The implementation of query caching in WP_User_Query aligns with that of other query classes. Once a query is executed, the resulting database queries are cached, and subsequent queries with the same parameters will retrieve the data from the cache. This caching behavior, when combined with persistent object caching, ensures that the database query won’t be executed again until the caches are invalidated, leading to a substantial reduction in overall database queries. Even sites using in-memory caching will benefit from avoiding repetitive queries, although the performance improvement may not be as significant.

It’s important to note that starting from this version onward, all calls to WP_User_Query will be automatically cached by default. However, if you wish to disable query caching for specific queries, you can simply set the cache_results parameter to false, as demonstrated in the following example:

$args = array(

   'number' => 50,

   'cache_results' => false

);

$query = new WP_User_Query( $args );

Alternatively, you can globally disable caching by using a filterFilter Filters are one of the two types of Hooks https://codex.wordpress.org/Plugin_API/Hooks. They provide a way for functions to modify data of other functions. They are the counterpart to Actions. Unlike Actions, filters are meant to work in an isolated manner, and should never have side effects such as affecting global variables and output., as shown below:

function disable_caching( $wp_user_query ) {

   $wp_user_query->query_vars['cache_results'] = false;

}

add_action( 'pre_get_users', 'disable_caching' );

For developers working on custom solutions, it’s essential to utilize Core functions such as wp_insert_user for adding users to the database. These functions are well-maintained and ensure proper cache invalidation. If you directly update the database, it is strongly recommended to call the clean_user_cache function after modifying the respective database row.

With this update, a new global cache group called user-queries is introduced to store the results of queries. If your site uses persistent object caching, make sure it supports adding global cache groups by utilizing the wp_cache_add_global_groups function, introduced in WP 2.6. If not, you will need to manually add the three new global cache groups.

It’s worth noting that caching will be disabled for user queries that utilize the field parameter and request more than 3 fields. This decision is made to prevent cache values from becoming excessively large and to avoid filling up caches with data unlikely to be reused.

Lastly, plugins utilizing the users_pre_query hook to modify the returned values will bypass caching and continue to function as they did in previous versions of WordPress.

For additional context on the changes, please see #40613.

Props to @flixos90 and @adamsilverstein for peer review, to @stevenlinx for review.

#6-3, #dev-notes, #dev-notes6-3

I18N Improvements in 6.3

Various internationalization (i18n) improvements are in WordPress 6.3, and this developers note will focus on these.

Allow to short-circuit load_textdomain()

In #58035 / [55928], a new pre_load_textdomain filterFilter Filters are one of the two types of Hooks https://codex.wordpress.org/Plugin_API/Hooks. They provide a way for functions to modify data of other functions. They are the counterpart to Actions. Unlike Actions, filters are meant to work in an isolated manner, and should never have side effects such as affecting global variables and output. was introduced. This is useful for plugins to develop and test alternative loading/caching strategies for translations. This brings consistency with the existing pre_load_script_translations filter for JavaScriptJavaScript JavaScript or JS is an object-oriented computer programming language commonly used to create interactive effects within web browsers. WordPress makes extensive use of JS for a better user experience. While PHP is executed on the server, JS executes within a user’s browser. https://www.javascript.com/. translations.

Improvements to just-in-time translationtranslation The process (or result) of changing text, words, and display formatting to support another language. Also see localization, internationalization. loading

In #58321, it was reported that _load_textdomain_just_in_time() was firing too often if no translations were found for a given text domain, which typically is the case on site running English (US).

[55865] addresses this issue, which resulted in some minor performance improvements.

Props to @spacedmonkey for technical review, to @stevenlinx for proofreading.

#6-3, #dev-notes, #dev-notes6-3, #i18n

Registering scripts with `async` and `defer` attributes in WordPress 6.3

WordPress 6.3 introduces support for registering scripts with async and defer attributes as part of an enhancementenhancement Enhancements are simple improvements to WordPress, such as the addition of a hook, a new feature, or an improvement to an existing feature. to coreCore Core is the set of software required to run WordPress. The Core Development Team builds WordPress.’s existing Scripts APIAPI An API or Application Programming Interface is a software intermediary that allows programs to interact with each other and share data in limited, clearly defined ways.. This addresses a long-standing Trac ticket, and adds the ability to define a loading strategy for scripts. Supported strategies are as follows:

  • Blocking (default, this strategy is not supplied)
  • Deferred (by supplying a defer loading strategy)
  • Asynchronous (by supplying an async loading strategy)

This enhancement was originally proposed in December 2022.

Why is this enhancement useful?

Adding defer or async to script tags enables script loading without “blocking” the rest of the page load, resulting in more performant sites via improved Largest Contentful Paint (LCP) performance. This leads to a better user experience. This has been common practice in web engineering for over a decade, yet to date there have been no core methods or a means of achieving this when registering/enqueuing scripts using core WordPress APIs.

Prior to this enhancement, developers have had to resort to less than ideal alternatives such as directly filtering the tags at the point of output (using the script_loader_tag filterFilter Filters are one of the two types of Hooks https://codex.wordpress.org/Plugin_API/Hooks. They provide a way for functions to modify data of other functions. They are the counterpart to Actions. Unlike Actions, filters are meant to work in an isolated manner, and should never have side effects such as affecting global variables and output., or worse the clean_url filter), or handling the tagtag A directory in Subversion. WordPress uses tags to store a single snapshot of a version (3.6, 3.6.1, etc.), the common convention of tags in version control systems. (Not to be confused with post tags.) output directly using wp_print_script_tag and the wp_script_attributes filter. Although fairly common practice (as it was the only means available prior), it is considered “hacky” as it does not take into account the dependency tree, or inline scripts for that matter, which can lead to interoperability issues or bugs with other scripts.

The difference between deferred (via the defer script attribute) and asynchronous (via the async script attribute) scripts is as follows:

  • Deferred scripts
    Scripts marked for deferred execution — via the defer script attribute — are only executed once the DOM tree has fully loaded (but before the DOMContentLoaded and window load events). Deferred scripts are executed in the same order they were printed/added in the DOM, unlike asynchronous scripts.
  • Asynchronous scripts
    Scripts marked for asynchronous execution — via the async script attribute — are executed as soon as they are loaded by the browser. Asynchronous scripts do not have a guaranteed execution order, as script B (although added to the DOM after script A) may execute first given that it may complete loading prior to script A. Such scripts may execute either before the DOM has been fully constructed or after the DOMContentLoaded event.

Summary of the changes

At a high level, the changes can be summarized as follows:

  • WordPress now adds support for specifying a script loading strategy via the wp_register_script() and wp_enqueue_script() functions.
  • These functions have new function signatures, with the prior $in_footer boolean parameter being overloaded to accept a new $args array parameter in order to facilitate an easy entry point for specifying a loading strategy for a script, while still retaining full backward compatibility for $in_footer implementations; this retains the means of specifying whether a script should be printed in the footer or not via a key within the new $args parameter. Note that the strategy can also be specified in a backwards-compatible way via wp_script_add_data().

Various additions and enhancements to the WP_Scripts class were made to facilitate the necessary business logic that prepares and outputs a script’s loading strategy.

Example 1: Specifying a loading strategy for a script

A loading strategy may be assigned via the wp_register_script() and wp_enqueue_script() functions by passing a strategy key value pair to the new/overloaded $args parameter. 

The following example showcases a new script with handle 'foo' being registered as a deferred script:

wp_register_script( 
	'foo', 
	'/path/to/foo.js', 
	array(), 
	'1.0.0', 
	array(
		'strategy' => 'defer'
	) 
);

This exact same means of specifying a loading strategy may be achieved via the wp_enqueue_script() function.

Example 2: Specifying that a script be printed in the footer via the new API

The next example showcases a second script being specified for footer printing using the new API, while also supplying the async loading strategy at the same time:

wp_register_script( 
	'bar', 
	'/path/to/bar.js', 
	array(), 
	'1.0.0', 
	array(
		'in_footer' => true,
		'strategy'  => 'async',
	)
)

This exact same means of specifying footer printing may be achieved via the wp_enqueue_script() function.

Implementation details

This feature enhances the existing Scripts API by providing a simple means of specifying a loading strategy by extending commonly used & well known aspects of the Scripts API. It also takes into consideration a script’s dependency tree (its dependencies and/or dependents) when deciding on an “eligible strategy” so as not to result in application of a strategy that is valid for one script but detrimental to others in the tree by causing an unintended out of order of execution. This is near-impossible to achieve via the prior means of adding script loading strategy attributes when using the alternative “hacky” means outlined in the section above.

Technical implementation of the script loading strategy enhancements have been undertaken within the existing Scripts API, notably within the WP_Scripts class, and via enhancements to the familiar and commonly used wp_register_script() and wp_enqueue_script() functions.

A note on dependencies vs dependents
To avoid confusion regarding terminology, let’s clarify the difference between a script’s dependencies vs. its dependents. A script’s dependencies refers to the scripts that said script itself depends on, i.e they must be enqueued prior to said script being enqueued. A script’s dependents on the other hand refers to the scripts that depend on said script, i.e scripts that define said script in their dependencies array.

Changes to the $in_footer parameter of wp_register_script() and wp_enqueue_script() functions

The most notable change to the existing wp_register_script() and wp_enqueue_script() functions is the function signature change, where $in_footer (previously a boolean parameter) has been overloaded to also accept an array $args parameter, with any of the following keys:

  • (bool) in_footer
    • Behaves just like prior implementation of the top level $in_footer param.
  • (string) strategy
    • Accepts an intended loading strategy for the given script being registered/enqueued. Acceptable string values available at the time of implementation are defer for deferred scripts and async for asynchronous scripts.
    • Defaults to blocking behavior, thus retaining backward compatibility for existing script registrations and enqueues.

Retaining backward compatibility

For prior/existing usage of the wp_register_script() and wp_enqueue_script() functions making use of the $in_footer boolean param, backward compatibility is retained via logic that explicitly sets the scripts group to the applicable value for footer or headerHeader The header of your site is typically the first thing people will experience. The masthead or header art located across the top of your page is part of the look and feel of your website. It can influence a visitor’s opinion about your content and you/ your organization’s brand. It may also look different on different screen sizes. printing based on the boolean value passed to the new/overloaded $args parameter. Thus, full backward compatibility is retained and this is a non-breaking enhancement of the API.

While the changes introduced within this feature themselves are considered non-breaking, when making use of the new $args parameter (replacing/overloading the previous $in_footer parameter) in a pluginPlugin A plugin is a piece of software containing a group of functions that can be added to a WordPress website. They can extend functionality or add new features to your WordPress websites. WordPress plugins are written in the PHP programming language and integrate seamlessly with WordPress. These can be free in the WordPress.org Plugin Directory https://wordpress.org/plugins/ or can be cost-based plugin from a third-party/theme/codebase powered by WordPress <6.3, there is one scenario where the $in_footer intention would be misunderstood by core. Take for example the following scenario:

wp_register_script( 
	'foo', 
	'/path/to/foo.js', 
	array(), 
	'1.0.0', 
	array( 
		'strategy'  => 'defer',
		'in_footer' => false, // Note: This is the default value.
	)
);

In WordPress >=6.3 this would be correctly evaluated as being printed in the head via the in_footer array key value being false (which is also the default).

In WordPress versions <6.3, however, the presence of the above array assigned to the $in_footer parameter would itself evaluate to a boolean value of true, the opposite of what may be intended by the developer. That being said, one could rightfully argue that in versions of WordPress that do not support deferred/asynchronous scripts, having them printed in the footer is the next best alternative.

The simplest way to prevent this interoperability problem is to pass the strategy via a different means than the $args param to wp_register_script() or wp_enqueue_script() functions.

It can be passed instead via wp_script_add_data(), in which case it will be understood by WordPress 6.3 but ignored by older versions.

wp_register_script( 
	'foo', 
	'/path/to/foo.js', 
	array(), 
	'1.0.0', 
	false
);
wp_script_add_data( 'foo', 'strategy', 'defer' );

Alternatively, interoperability between WordPress versions newer and older than 6.3 using a single function call per script can be achieved by wrapping script registrations/enqueues within a wrapper function that accounts for new and old function signatures, thus retaining total backward compatibility.

An example of such a script registration/enqueue wrapper may look something as follows:

myplugin_register_script( $handle, $src, $deps, $ver, $args ) { 
    global $wp_version;

    // If >= 6.3, re-use wrapper function signature.
    if ( version_compare( $wp_version,'6.3', '>=' ) ) { 
        wp_register_script(
            $handle,
            $src,
            $deps,
            $ver,
            $args
            );
        } else {
        // Extract in_footer value for older version usage.
        $in_footer = isset( $args['in_footer'] ) ? $args['in_footer'] : false;
        
        wp_register_script(
            $handle,
            $src,
            $deps,
            $ver,
            $in_footer
        );
    }
}

Intended vs. eligible loading strategies

It should be noted that while a developer may intend for a given script to contain a certain loading strategy, the final loading strategy may differ based on factors such as script dependencies/dependents and inline scripts. 

For example, if a developer registers script foo with a strategy of defer, its dependencies must either use a defer or blocking strategy and its dependents must use a defer strategy in order for the intended execution order to be maintained. If a dependent of said script foo is then registered with a blocking intended strategy, script foo and all of its dependencies would then become blocking.

Newly added logic within the WP_Scripts class is responsible for exercising a series of logical checks that ensure that the final strategy for a given script handle is the most eligible strategy based on the factors outlined further above.

A handle will never inherit a strategy that is “more strict” than the one intended, i.e. a script marked for deferred loading will never be changed to asynchronous loading, but the reverse may indeed be the outcome if environmental factors warrant it.

Inline scripts

There are some nuances when applying a loading strategy to scripts (or scripts in the dependency tree) that have inline scripts attached to them, as this ultimately has an effect on the final outcome of an intended/eligible strategy.

Inline scripts that are registered in the before position, remain largely unchanged in behavior, given that they will inherently be parsed and/or executed before the main/parent script is parsed for immediate, deferred or asynchronous execution.

Inline scripts that are registered in the after position (the default for wp_add_inline_script()), however, will affect the final loading strategy of a main/parent script if said main/parent script has an async or deferred eligible strategy. This is largely due to the complexity in ensuring that inline scripts attached to deferred/asynchronous scripts execute at the appropriate and expected time, while not having a negative impact on the parent script itself, and the dependency tree as a whole.

Therefore, if a given script handle contains inline scripts in the after position, this script will be assumed to be blocking and any intended strategy such as defer/async will be removed, with the final eligible strategy being blocking. This, in turn, may affect the script’s dependency tree, and all scripts within it, may too, be treated as blocking scripts in a bid to retain correct execution order and functionality of the enqueued scripts. Logic is explicitly employed to ensure correct execution order of the script tree in these instances.

A follow-up ticketticket Created for both bug reports and feature development on the bug tracker. #58632 has been opened to continue ongoing discussions and proof of concept implementations to potentially introduce delayed execution of inline after scripts in the future which would preserve their loading order.

Migrating to the new API

Code implementations making use of legacy methods of adding async or defer attributes to script tags should migrate to the new API. These include scenarios where the attributes have historically been added to a script tag by way of the script_loader_tag filter, or worse via the clean_url filter.

The following examples show implementation that make use of the script_loader_tag and clean_url filters, which are now considered less-than-ideal approaches, and then show how it should be done using the new API:

MigrationMigration Moving the code, database and media files for a website site from one server to another. Most typically done when changing hosting companies. Consideration Example 1: Adding a defer attribute via the script_loader_tag filter

function old_approach( $tag, $handle ) {
// Only affects foo script.
    if ( 'foo' !== $handle) {
        return $url;
    }

// Modern implementations may employ WP_HTML_Tag_Processor here.
    return str_replace( ' src=', ' defer src=', $tag );
}
add_filter( 'script_loader_tag', old_approach, 10, 2 );

Migration Consideration Example 2: Adding a defer attribute via the clean_url filter

// WARNING: THIS HAS ALWAYS BEEN BRITTLE AND IS NOT RECOMMENDED.
function old_brittle_approach( $url ) {
    // Only affects foo script.
    if ( false === strpos( $url, 'foo.js' ) ) {
        return $url;
    }

    return "$url' defer "; // Assumes single-quoted attributes!
}
add_filter( 'clean_url', 'old_brittle_approach' );

If you are using an approach similar to the one above to add defer or async attributes to a script, please migrate to the new API by using any one of the approaches outlined earlier in this post.

Props: @joemcgill @flixos90 @westonruter @adamsilverstein @jyolsna @stevenlinx

#6-3, #dev-notes, #dev-notes6-3

Image performance enhancements in WordPress 6.3

WordPress 6.3 comes with several enhancements that improve load time performance for content with images. The benefits can be seen in the Largest Contentful Paint metric (short “LCP”), which captures the time from the beginning of the request until the largest content element in the viewport has been rendered.

Summary of the changes

At a high level, the changes can be summarized as follows:

  • WordPress now automatically adds the fetchpriority attribute with a value of “high” to the image it determines most likely to be the “LCP image”, i.e. the image that is the largest content element in the viewport. The attribute tells the browser to prioritize this image, even before it has computed the layout, which typically improves LCP by 5-10%. See the original proposal post for additional context.
  • Further adjustments and fixes have been implemented to improve the automatic handling of lazy-loading via the loading attribute to more reliably detect when to omit the attribute from some images. This effort was started in WordPress 5.9 and continued in WordPress 6.2. Most recently, a holistic assessment of the remaining issues led to all of them being fixed in WordPress 6.3. See the relevant WordPress 5.9 dev note post for additional context on why not lazy-loading in-viewport images, especially the LCP image, is important for performance.

In order to implement the automated fetchpriority support, some refactoring was needed to decouple the logic for detecting in-viewport images from the lazy-loading specific functionality. As a result, the fetchpriority and loading attributes are now controlled by a single function in WordPress coreCore Core is the set of software required to run WordPress. The Core Development Team builds WordPress.. If you have been relying on specific parts of WordPress core’s lazy-loading logic in your pluginPlugin A plugin is a piece of software containing a group of functions that can be added to a WordPress website. They can extend functionality or add new features to your WordPress websites. WordPress plugins are written in the PHP programming language and integrate seamlessly with WordPress. These can be free in the WordPress.org Plugin Directory https://wordpress.org/plugins/ or can be cost-based plugin from a third-party or theme, please continue reading to learn more about those changes.

New function wp_get_loading_optimization_attributes()

A new function wp_get_loading_optimization_attributes( string $tag_name, array $attr, string $context ) is introduced in WordPress 6.3, which returns an associative array of additional attributes and their values. This provides a central place to get HTMLHTML HyperText Markup Language. The semantic scripting language primarily used for outputting content in web browsers. attributes that potentially improve the load time performance of images. For now, the function can only return a fetchpriority or loading attribute (or neither if not applicable), though this may be enhanced with other performance-related attributes in the future.

The new function is now used everywhere in WordPress core where fetchpriority or loading attributes are handled for images, most importantly:

  • Handling in-content images, via wp_filter_content_tags()
  • Handling programmatically rendered images, via wp_get_attachment_image() / get_the_post_thumbnail()
  • Handling avatarAvatar An avatar is an image or illustration that specifically refers to a character that represents an online user. It’s usually a square box that appears next to the user’s name. images, via get_avatar()
  • Handling the custom headerHeader The header of your site is typically the first thing people will experience. The masthead or header art located across the top of your page is part of the look and feel of your website. It can influence a visitor’s opinion about your content and you/ your organization’s brand. It may also look different on different screen sizes. image, via get_header_image_tag() (support added via #58680)
  • Handling images within shortcodes, via do_shortcode() (support added via #58681)
  • Handling images in the image widgetWidget A WordPress Widget is a small block that performs a specific function. You can add these widgets in sidebars also known as widget-ready areas on your web page. WordPress widgets were originally created to provide a simple and easy-to-use way of giving design and structure control of the WordPress theme to the user., via the WP_Widget_Media_Image class (support added via #58704)

That by itself is an improvement already, leading to consistent behavior across the board. Prior to this change, the above functions were using slightly different implementations for lazy-loading which could potentially lead to issues.

In order to use the function, pass the HTML element’s tagtag A directory in Subversion. WordPress uses tags to store a single snapshot of a version (3.6, 3.6.1, etc.), the common convention of tags in version control systems. (Not to be confused with post tags.) name and attributes to the new function, alongside a context string depending on how the image is being rendered in WordPress (for example “the_content”, or “wp_get_attachment_image”). Providing the tag name is important as the function is not limited to only images. At the moment it supports “img” tags (for fetchpriority and loading attributes) and “iframeiframe iFrame is an acronym for an inline frame. An iFrame is used inside a webpage to load another HTML document and render it. This HTML document may also contain JavaScript and/or CSS which is loaded at the time when iframe tag is parsed by the user’s browser.” tags (for loading attributes), though this may be expanded in the future. The return value of the function is always an array, which can be merged into the tag’s existing array of attributes, or alternatively specific attribute values can be extracted as needed.

Here you can see an example on how to potentially use the function:

$attr = array(
	'src'    => 'my-image.jpg',
	'width'  => 500,
	'height' => 300,
);
$attr = array_merge(
	$attr,
	wp_get_loading_optimization_attributes( 'img', $attr, 'wp_get_attachment_image' )
);
echo '<img';
foreach ( $attr as $key => $value ) {
	echo ' ' . sanitize_key( $key ) . '="' . esc_attr( $value ) . '"';
}
echo '>';

Note that the example uses the “wp_get_attachment_image” context, which is typically used only in the wp_get_attachment_image() function. While you could provide another arbitrary value here, at the moment it is advisable to use the most suitable core context as WordPress core only handles those specifically. Assuming that this is a generic image being rendered, the “wp_get_attachment_image” context works well.

Which additional attributes are returned in the above example depend on the specific context and place where the function is called. If the image is the first large image rendered in the response, the return value would be array( 'fetchpriority' => 'high' ). If it is an image that is likely further down the page, the return value would be array( 'loading' => 'lazy' ). There is also a chance that the array would be empty; for example, if the image is likely in the viewport but not large enough to be eligible as the LCP image.

Note that the function will never return both fetchpriority="high" and loading="lazy" for the same image, as those two attribute-value combinations are mutually exclusive and should never be used on the same element: lazy-loading an image while also marking it as high priority is an anti-pattern that should be avoided.

Rendering a custom header image in your theme

Custom header images are a feature that classic themes have supported for many years. With WordPress 6.3, the get_header_image_tag() function has received support to automatically include relevant loading optimization attributes, using the new wp_get_loading_optimization_attributes() function.

If you are developing or maintaining a theme that has custom header image support, it is advisable to use the get_header_image_tag() function to render the image. If you’re concerned about supporting WordPress versions prior to when this function was introduced in WordPress 4.4, see #58675 for how older core themes are updated to use this function when available.

If for some reason you are unable to use the get_header_image_tag() function and need to render the image tag manually while retrieving the image URLURL A specific web address of a website or web page on the Internet, such as a website’s URL www.wordpress.org with the get_header_image() function, you can still ensure the loading optimization attributes are applied. For example consider the following code:

$attr = array(
	'src'    => get_header_image(),
	'width'  => (int) get_custom_header()->width,
	'height' => (int) get_custom_header()->height,
);
$attr = array_merge(
	$attr,
	wp_get_loading_optimization_attributes( 'img', $attr, 'get_header_image_tag' )
);
echo '<img';
foreach ( $attr as $key => $value ) {
	echo ' ' . sanitize_key( $key ) . '="' . esc_attr( $value ) . '"';
}
echo '>';

Additionally, if you are certain that the header image is also the LCP image of the page, you can manually mark it as such, by adding 'fetchpriority' => 'high' to the $attr array before calling wp_get_loading_optimization_attributes(). Calling the function would still be crucial to ensure WordPress core considers the image so that subsequent images on the page also receive the correct attributes.

Customization of image priority and lazy-loading behavior

With the new function being used consistently anywhere images are rendered in WordPress core, support for customizing is also improved. The function will never override attributes that are already provided, so if you set a fetchpriority or loading attribute on an image before this function is called, the attribute will be kept as is. This allows fine tuning by not enforcing the default automated behavior. If doing so, keep in mind never to set both fetchpriority="high" and loading="lazy" for an element. If the function encounters those two attribute-value combinations together, it will trigger a warning.

Modifying the default behavior for lazy-loading works just like before. Relevant filters like wp_lazy_loading_enabled, wp_img_tag_add_loading_attr, and wp_omit_loading_attr_threshold have not been modified in this release. However, the default value for the wp_omit_loading_attr_threshold filterFilter Filters are one of the two types of Hooks https://codex.wordpress.org/Plugin_API/Hooks. They provide a way for functions to modify data of other functions. They are the counterpart to Actions. Unlike Actions, filters are meant to work in an isolated manner, and should never have side effects such as affecting global variables and output. has been modified from 1 to 3 (see #58213 for context) in order to better support the common pattern of multi-column layouts with images. Please refer to the lazy-loading dev notedev note Each important change in WordPress Core is documented in a developers note, (usually called dev note). Good dev notes generally include a description of the change, the decision that led to this change, and a description of how developers are supposed to work with that change. Dev notes are published on Make/Core blog during the beta phase of WordPress release cycle. Publishing dev notes is particularly important when plugin/theme authors and WordPress developers need to be aware of those changes.In general, all dev notes are compiled into a Field Guide at the beginning of the release candidate phase. posts for WordPress 5.5 and for WordPress 5.9 for more information on those existing filters.

For fetchpriority, a new filter wp_min_priority_img_pixels was introduced, which allows developers to modify the size threshold above which an image is considered eligible to receive fetchpriority="high". This minimum threshold value is in place to ensure only images of a certain size are considered for the LCP image. For example, a small icon would never be the LCP image — even if there were no other images above the fold, the LCP element would most likely be a non-image element, e.g. a heading.

The size is defined as a the product of “width * height” of the image, and the default threshold is 50,000. For example, this means that an image with a width of 300 pixels and height of 200 pixels is eligible (since 300*200=60000) while an image with a width of 200 pixels and height of 200 pixels is not (since 200*200=40000).

Here is an example: Imagine you have an image that is 200×200 pixels, and despite being that small, you know it is the LCP image of the specific URL it appears on. You could then use the new filter to ensure the image can receive fetchpriority="high".

add_filter(
	'wp_min_priority_img_pixels',
	function( $size ) {
		if ( is_this_the_url_with_the_image() ) {
			return 200 * 200; // Images at least as big as 200x200 
		}
		return $size;
	}
);

Keep in mind that in such a situation you could alternatively provide fetchpriority="high" on the image manually, which is just another arguably more direct way to achieve the same result. Therefore this approach is better suited for dynamic content in layouts where you might not know the exact image.

Deprecated functions

With the new wp_get_loading_optimization_attributes() function controlling both fetchpriority and loading attributes, a few existing functions are being deprecated as part of the WordPress 6.3 release.

wp_get_loading_attr_default() is superseded by the new function and therefore should not be used anymore. If you are currently calling this function in your plugin, you can use code such as the one below to remain compatible with WordPress versions before and after this deprecation:

function myplugin_get_loading_attr_default( $tag_name, $attr, $context ) {
	// For WP >= 6.3.
	if ( function_exists( 'wp_get_loading_optimization_attributes' ) ) {
		$loading_optimization_attr = wp_get_loading_optimization_attributes( $tag_name, $attr, $context );
		if ( isset( $loading_optimization_attr['loading'] ) ) {
			return $loading_optimization_attr['loading'];
		}
		return false;
	}

	// For WP < 6.3.
	return wp_get_loading_attr_default( $context );
}

While the above works well for a transition period, for the long term consider also supporting fetchpriority, e.g. by using the new function directly.

The other function that has been deprecated is wp_img_tag_add_loading_attr(), as this function is superseded by a new wp_img_tag_add_loading_optimization_attrs(), which also encompasses the fetchpriority enhancementenhancement Enhancements are simple improvements to WordPress, such as the addition of a hook, a new feature, or an improvement to an existing feature.. Note that both of these functions are intended for parsing HTML content from the database, so it is advisable for plugins to not rely on either of them. Try to use wp_get_loading_optimization_attributes() or a wrapper function like the above instead to add loading optimization attributes to a specific image.

Please see #58235 for more information on fetchpriority support.

Lazy-loading issues addressed

As mentioned before, WordPress 6.3 addresses several outstanding problems where loading="lazy" was being added to images that should not receive it. Other than the aforementioned change of the default value for the wp_omit_loading_attr_threshold filter from 1 to 3 (see #58213), those fixes do not affect the underlying developer APIs in any way.

Here is a list of the other lazy-loading bugs that were addressed:

  • Images in the header (before “the loopLoop The Loop is PHP code used by WordPress to display posts. Using The Loop, WordPress processes each post to be displayed on the current page, and formats it according to how it matches specified criteria within The Loop tags. Any HTML or PHP code in the Loop will be processed on each post. https://codex.wordpress.org/The_Loop.”) in classic themes are now eligible for having the loading=”lazy” attribute omitted. See #58211.
    • For blockBlock Block is the abstract term used to describe units of markup that, composed together, form the content or layout of a webpage using the WordPress editor. The idea combines concepts of what in the past may have achieved with shortcodes, custom HTML, and embed discovery into a single consistent API and user experience. themes, this was already fixed in WordPress 6.2, via #56930.
  • Programmatically rendered images which are injected into post content no longer incorrectly affect the lazy-loading logic. See #58089.
  • Rendering an excerptExcerpt An excerpt is the description of the blog post or page that will by default show on the blog archive page, in search results (SERPs), and on social media. With an SEO plugin, the excerpt may also be in that plugin’s metabox. where the full post contains images no longer incorrectly affects the lazy-loading logic. See #56588.
  • A duplicate call to get the loading attribute value in get_the_post_thumbnail() has been removed. See #58212.
  • Images before “the loop” are now counted towards the threshold for not lazy-loading. See #58635.

Props @westonruter @joemcgill for technical review, @mukesh27 @stevenlinx for proofreading.

#6-3, #dev-notes, #dev-notes6-3

Improvements to the metadata API in WordPress 6.3

WordPress 6.3 brings significant improvements to the metadata APIAPI An API or Application Programming Interface is a software intermediary that allows programs to interact with each other and share data in limited, clearly defined ways., enhancing the lazy loading capabilities for term, comment, and site metadata. These enhancements aim to improve performance, optimize code readability, and ensure a consistent developer experience across different metadata types.

For context: Lazy loading metadata in WordPress refers to a technique where metadata associated with various elements, such as terms, is loaded only when it is actually needed. Instead of fetching and loading all metadata upfront, the metadata is deferred to a queue until the specific metadata type is requested, reducing unnecessary database queries or cache lookups and improving overall performance. 

Term metadata lazy loading improvements

Term metaMeta Meta is a term that refers to the inside workings of a group. For us, this is the team that works on internal WordPress sites like WordCamp Central and Make WordPress. in WordPress has always been lazily loaded since its introduction in WordPress 4.4. However, this behavior was only applicable to WP_Query. In other CoreCore Core is the set of software required to run WordPress. The Core Development Team builds WordPress. components like WP_Term_Query, term meta was always primed by default, unless developers explicitly set the update_term_meta_cache parameter to false. Unfortunately, many developers overlooked this parameter, resulting in unnecessary loading of term meta. 

In WordPress 6.3, WP_Term_Query has been improved so that instead of priming term meta, which involves performing a database or cache lookup, terms are now added to a queue dedicated to loading term meta. This queue, implemented as an array stored in memory, offers excellent performance. Additionally, it allows developers to conveniently include term IDs if they anticipate requiring them later. Only when the first call to get_term_meta is made, will all the term meta be primed in a single request.

To facilitate the lazy loading of term meta, a new function called wp_lazyload_term_meta() has been introduced in Core. This function provides an efficient mechanism for handling term meta and contributes to optimizing the overall performance of WordPress. To use, pass an array of term IDs to the function like this: 

wp_lazyload_term_meta( array( 1, 2, 3 ) );

For more information, please check out the original ticketticket Created for both bug reports and feature development on the bug tracker. #57645.

WordPress 6.3 introduces further enhancements to the handling of term meta in WP_Query.  Currently, in the WP_Query class, the function wp_queue_posts_for_term_meta_lazyload is invoked, which adds the term IDs of all terms linked to the queried posts into the lazy load metadata queue. Improvements to this function were made in WordPress 6.2, which improved the performance of this function by utilizing wp_cache_get_multiple()

In this new release, an unnecessary check to verify term existence has been eliminated. As a result, when wp_queue_posts_for_term_meta_lazyload() is called, it no longer executes get_term for every individual term ID, as it is highly unlikely that the term would not exist. See #57966

There were also a number of other places where term meta was not loaded at all as it is completely unneeded. See #58230, #57701

Comment metadata lazy loading improvements

Similar to term meta, comment meta in WordPress was previously lazily loaded only within the context of WP_Query. Now, rather than priming comment meta in the WP_Comment_Query class, it is added to the lazily loaded metadata queue and loaded only when used. This keeps the logic consistent between term and comment meta and also makes the code much more readable. As a result, the wp_queue_comments_for_comment_meta_lazyload() function, which is now unnecessary, has been deprecated. See #57801, #58301

Site metadata is now lazily loaded

Site meta was introduced in #40647 and allows developers to extend multisitemultisite Used to describe a WordPress installation with a network of multiple blogs, grouped by sites. This installation type has shared users tables, and creates separate database tables for each blog (wp_posts becomes wp_0_posts). See also network, blog, site functionality by adding metadata to a site on the multisite. Site meta is not to be confused with networknetwork (versus site, blog) meta, which is used for network options. Site meta is only used in one place in core and designed for pluginPlugin A plugin is a piece of software containing a group of functions that can be added to a WordPress website. They can extend functionality or add new features to your WordPress websites. WordPress plugins are written in the PHP programming language and integrate seamlessly with WordPress. These can be free in the WordPress.org Plugin Directory https://wordpress.org/plugins/ or can be cost-based plugin from a third-party use. 

Previously, when calling WP_Site_Query or get_sites(), developers had to pass an update_site_meta_cache parameter to the query to ensure that site meta was not primed, resulting in a cache lookup or database query. Now, whenever update_site_meta_cache is true, site ids are added to the queue to be lazily loaded. If get_site_meta() is called, all the IDs in the queue are loaded in a single call. 

A new function has been added in core to add site IDs to the queue called wp_lazyload_site_meta(). It can be used by passing an array of site IDs, like this:

wp_lazyload_site_meta( array( 1, 2, 3 ) );

For more information, please check out the original ticket #58185.

General improvements to the Lazy loading API

The WP_Metadata_Lazyloader class, responsible for lazy loading metadata, underwent a significant refactor to enhance code maintainability. As part of this refactoring, the lazyload_term_meta and lazyload_comment_meta methods of the class have been deprecated. These methods have been replaced with a more versatile lazyload_meta_callback method, which can be reused for any metadata type. 

If your code currently utilizes the lazyload_term_meta and lazyload_comment_meta methods, it is recommended that you transition to using lazyload_meta_callback. This improvement allows for easy extension of the metadata data API to support additional metadata types in the future, such as posts and users.

Further enhancements were made to the lazy loading metadata API. A check is now implemented to verify if the requested ID is already present in the queue before processing. Here’s an example scenario: Let’s say your page request has added three items (IDs 5, 6, and 7) to the metadata lazy loading queue. However, when you call the get_term_meta function, you request ID 9, which is not in the queue. Previously, this would have led to an additional database or cache lookup. With the latest improvement, the queue is checked before processing to see if the current ID is already in the queue. If it’s not, the ID is added to the queue, preventing unnecessary lookups. See #57901

There have been significant changes in how metadata is handled for comments, terms, and sites. In the updated implementation, the prime meta parameters in functions such as _prime_term_cache() and WP_Term_Query are always respected. However, instead of directly priming the meta, it is now added to the metadata queue. Previously, there were inconsistencies in these functions and classes. For example, in _prime_term_cache(), if the term was already present in the cache, the term meta would not be primed. This led to a confusing developer experience and degraded performance.

Now, with the latest improvements, adding an ID to the queue has minimal to no performance impact when requested. As a result, these functions and classes are expected to behave in a more predictable manner. Developers can rely on consistent behavior and improved performance regarding metadata handling for comments, terms, and sites. See #57227

Props to @flixos90 and @joemcgill for peer review, to @stevenlinx, @leonnugraha and @costdev for review.

#6-3, #dev-notes, #dev-notes6-3

New in 6.3: Rollback for failed manual plugin and theme updates

Should the manual pluginPlugin A plugin is a piece of software containing a group of functions that can be added to a WordPress website. They can extend functionality or add new features to your WordPress websites. WordPress plugins are written in the PHP programming language and integrate seamlessly with WordPress. These can be free in the WordPress.org Plugin Directory https://wordpress.org/plugins/ or can be cost-based plugin from a third-party or theme update process fail, the rollback feature will automatically restore the previously installed version to ensure the website remains available to its users.

The Rollback feature originated in #51857.

When updating a plugin or theme, the old version of the plugin or theme is moved to a wp-content/upgrade-temp-backup/plugins/PLUGINNAME or wp-content/upgrade-temp-backup/themes/THEMENAME folder.

The reason we chose to move instead of zip, is because zipping/unzipping are resources-intensive processes, and could increase the risk of failure on low-end, shared hosts. Moving files, on the other hand, is performed instantly and won’t be a bottleneck.

Moving is accomplished by means of the new move_dir() function, included in WordPress 6.2. PHPPHP The web scripting language in which WordPress is primarily architected. WordPress requires PHP 5.6.20 or higher’s rename() is used for this with a fallback to copy_dir(), a recursive file copy used by CoreCore Core is the set of software required to run WordPress. The Core Development Team builds WordPress. for a long time. move_dir()uses the ::move()method for the WP_Filesystem_DirectWP_Filesystem_FTPextWP_Filesystem_ftpsockets, and WP_Filesystem_SSH2 filesystem abstractions and has a fallback to copy_dir(). Find out more in the move_dir() devnote.

If the update process fails, then the backup we moved in the “upgrade-temp-backup” folder is restored to its original location. If the update succeeds, then the backup is deleted.

Two new checks were added in the Site Health screen:

  • Check to make sure that the backup folders are writable.
  • Check there is enough disk-space available to safely perform updates.

To avoid confusion: The “upgrade-temp-backup” folder will NOT be used to “roll back” a plugin/them to a previous version after a successful update. You can use the various rollback plugins for this.

The “upgrade-temp-backup” folder will simply contain a transient backup of the previously installed plugin or theme getting updated, and as soon as the update process finishes, the folder will be emptied.

When a rollback occurs, the user should simply see that there is an update pending and their site should still be working.

The simplest explanation of what results in a manual update failure and a rollback is anything that returns a WP_Error from WP_Upgrader::install_package().

  1. A bad request, something missing the source or destination of the update.
  2. WP_Error returned from the upgrader_pre_installupgrader_source_selectionupgrader_clear_destinationupgrader_post_install filters.
  3. An empty download package.
  4. A failure when moving the moving the installed plugin/theme to the temp-backup directory.
  5. If the remote source destination folder not able to be cleared and something is there.
  6. Unable to create remote destination folder.
  7. Unable to move/copy the the update to the remote destination.

Updated August 2, 2023 @afragen

The above encompasses parts 1 and 2 of the Rollback feature. Part 3, hopefully for WordPress 6.4, is the same process but for automatic updates. Specifically, Rollback part 3 checks to see that the updated plugin does not cause a PHP fatal error when activated. If it does, this error is captured and the previously installed version is restored.

All of the Rollback feature, parts 1-3, are included for testing in the Rollback Update Failure feature pluginFeature Plugin A plugin that was created with the intention of eventually being proposed for inclusion in WordPress Core. See Features as Plugins..

Props @costdev for peer review, @stevenlinx and @desrosj for review.

#6-3, #dev-notes, #dev-notes6-3, #props