The WordPress coreCoreCore is the set of software required to run WordPress. The Core Development Team builds WordPress. development team builds WordPress! Follow this site forย general updates, status reports, and the occasional code debate. Thereโs lots of ways to contribute:
Found a bugbugA bug is an error or unexpected result. Performance improvements, code optimization, and are considered enhancements, not defects. After feature freeze, only bugs are dealt with, with regressions (adverse changes from the previous version) being the highest priority.?Create a ticket in the bug tracker.
WordPress 6.9 introduced a fix for adjacent post navigation when posts have identical publication dates. While this resolved a long-standing bugbugA bug is an error or unexpected result. Performance improvements, code optimization, and are considered enhancements, not defects. After feature freeze, only bugs are dealt with, with regressions (adverse changes from the previous version) being the highest priority., it inadvertently caused infinite loops in some extensions that modify the get_adjacent_post()WHERE clause.
What Changed in WordPress 6.9
In WordPress 6.9 (Trac #8107), a bug fix landed where next/previous post navigation failed when multiple posts shared identical post_date values. This commonly occurred when bulk-publishing draft posts.
The Technical Change
The get_adjacent_post() functionโs WHERE clause was modified to include ID-based comparison as a tiebreaker:
Before (WordPress 6.8 and earlier):
WHERE p.post_date > '2024-01-01 12:00:00'
AND p.post_type = 'post'
After (WordPress 6.9):
WHERE (
p.post_date > '2024-01-01 12:00:00'
OR (
p.post_date = '2024-01-01 12:00:00'
AND p.ID > 123
)
)
AND p.post_type = 'post'
This ensures deterministic ordering when posts have identical dates, using the post ID as a secondary sort criterion.
Additionally, the ORDER BY clause was updated:
-- Before
ORDER BY p.post_date DESC
LIMIT 1
-- After
ORDER BY p.post_date DESC,
p.ID DESC
LIMIT 1
The Problem: Infinite Loops in Some Themes/Plugins
As Trac ticket #64390 documents, some plugins and themes modify adjacent post navigation to change behavior. For example, WooCommerceโs Storefront theme navigates between products instead of regular posts. These plugins use the get_{$adjacent}_post_wherefilterFilterFilters 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. to replace the current postโs date with a different postโs date.
Hereโs what was happening:
PluginPluginA 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-partyhooksHooksIn WordPress theme and development, hooks are functions that can be applied to an action or a Filter in WordPress. Actions are functions performed when a certain event occurs in WordPress. Filters allow you to modify certain functions. Arguments used to hook both filters and actions look the same. into get_previous_post_where filter.
Plugin does string replacement: replaces $current_post->post_date with $target_product->post_date.
Problem: The new WHERE clause structure includes the post date in TWO places (date comparison AND ID comparison).
Simple string replacement modified the date comparison but left the ID comparison unchanged.
Query returns the same post repeatedly โ infinite loopLoopThe 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..
Real-World Example: Storefront Theme
The fix to the WooCommerce Storefront theme illustrates this issue. They had to add special handling for the ID comparison:
// Replace the post date (works as before)
$where = str_replace( $post->post_date, $new->post_date, $where );
// NEW: Also need to replace the ID comparison (WordPress 6.9+)
if ( strpos( $where, 'AND p.ID ' ) !== false ) {
$search = sprintf( 'AND p.ID %s ', $this->previous ? '<' : '>' );
$target = $search . $post->ID;
$replace = $search . $new->ID;
$where = str_replace( $target, $replace, $where );
}
For Plugin Developers: Detecting and Fixing the Issue
Performs string replacement on post dates in the WHERE clause.
Changes which post is considered โadjacentโ (like navigating between custom post types instead).
To test if your plugin works correctly with WordPress 6.9:
Create 3-4 posts with identical publication dates (bulk publish drafts).
Navigate between them using your pluginโs adjacent post functionality.
Verify that navigation moves to different posts (not the same post repeatedly).
Check for infinite loops or performance issues.
Quick Fix: Handle the ID Comparison
If youโre doing date replacement in the WHERE clause, you also need to handle the ID comparison. There are numerous ways to tie a knot, but the following example is loosely inspired by Storefrontโs recent fix.
add_filter( 'get_next_post_where', 'example_custom_adjacent_post_where', 10, 5 );
add_filter( 'get_previous_post_where', 'example_custom_adjacent_post_where', 10, 5 );
function example_custom_adjacent_post_where( $where, $in_same_term, $excluded_terms, $taxonomy, $post ) {
// IMPORTANT: Replace this with your logic to find the desired adjacent post.
$adjacent_post = example_find_adjacent_post_function( $post );
if ( $adjacent_post instanceof WP_Post ) {
// Replace the date comparison.
$where = str_replace( $post->post_date, $adjacent_post->post_date, $where );
// Replace the post ID in the comparison.
$where = preg_replace(
"/AND p\.ID (<|>) {$post->ID}\)/",
"AND p.ID $1 {$adjacent_post->ID})",
$where
);
}
return $where;
}
You could also add a version_compare( $wp_version, '6.9', '>=' ) test if youโd like to support multiple versions.
Important: donโt forget to first test any code on your site, and customize it according to your needs.
Next time
At the time, this was not a known breaking change, and was classed as a bug fix.
However as @jmdoddpoints out in the ticket, changes to WP_Query, especially SQL changes should be, by default, communicated more widely. Going forward:
Reach out to known plugins using the get_{$adjacent}_post_where filter.
Include a migrationMigrationMoving the code, database and media files for a website site from one server to another. Most typically done when changing hosting companies. guidance in the 6.9 field guideField guideThe field guide is a type of blogpost published on Make/Core during the release candidate phase of the WordPress release cycle. The field guide generally lists all the dev notes published during the beta cycle. This guide is linked in the about page of the corresponding version of WordPress, in the release post and in the HelpHub version page. (as applicable).
Test against any known, popular plugins that modify adjacent post queries.
Whatโs Next and Getting Help
Discussions have started on the ticket about ways to make this more robust in future WordPress versions. If youโre experiencing issues related to this change:
Thank you for your patience and understanding. If you maintain a plugin affected by this change, please update it using the guidance above, and donโt hesitate to reach out if you need assistance.
WordPress 6.9 now includes a built-in feature to hide blocks, making it easy to tuck content away without deleting it. You can now hide blocks: select a blockBlockBlock 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., click the ellipsis, and choose โHideโ. Hidden blocks are visually removed from the editor, and fully omitted from the published markup. Scripts and styles for hidden blocks are also omitted from the rendered page by default (see WordPress 6.9 Frontend Performance Field Guide for more details).
To unhide a block, open the List View, identify hidden blocks via the โHiddenโ icon next to them, open the ellipsis menu again, and choose โShowโ. You can also toggle Hide/Show from the keyboard: use Ctrl + Shift + H on Windows orย Linux, โ + Shift + H on macOS.
How to disable the hide option
Because it is implemented as a standard Block APIAPIAn 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. support flag, opting into or out of this capabilitycapabilityAย capabilityย is permission to perform one or more types of task. Checking if a user has a capability is performed by the current_user_can function. Each user of a WordPress site might have some permissions but not others, depending on theirย role. For example, users who have the Author role usually have permission to edit their own posts (the โedit_postsโ capability), but not permission to edit other usersโ posts (the โedit_others_postsโ capability). aligns with the rest of the block supports.
The support is enabled by default for every block type except for a short list of coreCoreCore is the set of software required to run WordPress. The Core Development Team builds WordPress. blocks. To disable the support selectively, hook into the block_type_metadata() filter, adjust the metadata, and update the supports.visibility flag:
function disable_block_visibility_support( $metadata ) {
// Disable visibility support for the core/group block.
if ( isset( $metadata['name'] ) && 'core/group' === $metadata['name'] ) {
$metadata['supports']['visibility'] = false;
}
return $metadata;
}
add_filter( 'block_type_metadata', 'disable_block_visibility_support' );
BlockBlockBlock 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. Variations: Haveย getActiveBlockVariationย fall back to default variation
WordPress 6.9 introduces aย small changeย to active block variation detection.
To understand what that change looks like, recall that a block variation can include anย isActiveย criterionย that will be used to determine if a given block instance matches the variation.
The change concerns the case in which none of theย isActiveย criteria of a block typeโs variations match a given block instance. In this case, WordPress 6.9 will use the variation with theย isDefaultย propertyย set toย trueย as fallback, if any.
Two limitations apply:
The variation with theย isDefaultย property is only used as fallback if it doesnโtย alsoย include anย isActiveย criterion, as that would mean that the variation did not match that criterion (and thus cannot be assumed to be active).
The fallback is only applied for theย blockย andย transformย scopes but not to theย inserter, as to not affect the block name displayed there.
In practice, the updated behavior should be consistent with usersโ and developersโ expectations, as theย isDefaultย variation is typically used to set a default block variation different from the โvanillaโ block type.
Together with the improvements made to active block variation detectionย in WordPress 6.6ย , this change allows block authors to express the criteria for active block detection in a very succinct and JSONJSONJSON, or JavaScript Object Notation, is a minimal, readable format for structuring data. It is used primarily to transmit data between a server and web application, as an alternative to XML.-only manner. For an example, see how these principles were applied toย rewrite Group block variations.
CoreCoreCore is the set of software required to run WordPress. The Core Development Team builds WordPress.-data: Media Entity Deprecation
What is being deprecated?
In WordPress 6.9, theย @wordpress/core-dataย packageโsย mediaย entity is being deprecated. This entity is usually accessed through a range of APIs:
Theย getMediaย andย getMediaItemsย selectors or resolvers.
Theย saveMediaย andย deleteMediaย actions.
Theย getEntityRecord,ย getEntityRecordsย and other selectors or resolvers when passed theย 'root'ย andย 'media'ย params (e.g.ย getEntityRecords( 'root', 'media' );).
Theย editEntityRecord,ย saveEntityRecord,ย deleteEntityRecordย and other actions when passed theย 'root'ย andย 'media'ย params (e.g.ย deleteEntityRecord( 'root', 'media', 123)).
These APIAPIAn 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. calls will continue to work in WordPress 6.9, but will now log a deprecation message that recommends switching to a different signature.
Why is this being deprecated?
Theย mediaย entity was found to be duplicating another entity,ย attachment. This attachment entity is usually accessed through API calls likeย getEntityRecords( 'postType', 'attachment' );. These two entities both internally use theย mediaย REST APIREST APIThe REST API is an acronym for the RESTful Application Program Interface (API) that uses HTTP requests to GET, PUT, POST and DELETE data. It is how the front end of an application (think โphone appโ or โwebsiteโ) can communicate with the data store (think โdatabaseโ or โfile systemโ) https://developer.wordpress.org/rest-api/. endpoint and return results forย attachmentย post types.
The entity system maintains a cache of REST API data, and the two separate entity types result in two separate caches that might contain different states for the same data. If the caches become desynchonized, this can result in noticeable bugs where a user interface might show outdated data, or old data overwrites newer data when saved.
Various solutions for fixing this issue were explored, but the option that was found to be safest was to deprecate the media entity in favor of the attachment entity.
How to update your code to use theย attachmentย entity
Replacing API calls
The following code snippet shows some examples of how to update your API calls:
wp.data.select( 'core' ).getMedia( 123 ); // now deprecated
wp.data.select( 'core' ).getEntityRecord( 'root', 'media', 123 ); // now deprecated
wp.data.select( 'core' ).getEntityRecord( 'postType', 'attachment', 123 ); // recommended update
wp.data.select( 'core' ).getMediaItems(); // now deprecated
wp.data.select( 'core' ).getEntityRecords( 'root', 'media' ); // now deprecated
wp.data.select( 'core' ).getEntityRecords( 'postType', 'attachment' ); // recommended update
wp.data.dispatch( 'core' ).saveMedia( 123, { // ... media data } ); // now deprecated
wp.data.dispatch( 'core' ).saveEntityRecord( 'root', 'media', 123, { // ... media data } ); // now deprecated
wp.data.dispatch( 'core' ).saveEntityRecord( 'postType', 'attachment', 123, { // ... media data } ); // recommended update
wp.data.dispatch( 'core' ).deleteMedia( 123 ); // now deprecated
wp.data.dispatch( 'core' ).deleteEntityRecord( 'root', 'media', 123 ); // now deprecated
wp.data.dispatch( 'core' ).deleteEntityRecord( 'postType', 'attachment', 123 ); // recommended update
Changes to theย captionย property when using theย getEditedEntityRecordย selector
When making an API call for the โeditedโ version of an entity record (e.g.ย getEditedEntityRecord( 'root', 'media', 123 )), the core data package converts theย captionย property to a string:
caption: "My image's caption"
The attachment entity doesnโt perform the same processing, an API call likeย getEditedEntityRecord( 'postType', 'attachment', 123 )ย returns theย captionย in its object form:
caption: {
raw: "My image's caption",
rendered: "My image's caption"
}
Theย caption.rawย property corresponds to the string value that theย mediaย entity returns for theย caption.
When switching to the โattachmentโ entity, some code may need to be updated to account for this difference.
Block Editor: Support passing updater function to setAttributes
Starting from WordPress 6.9, theย setAttributeย prop passed to a blockโsย Editย component will now support an updater function as an argument.
The updater is a pure function; it takes only the current attributes as its argument and returns the updated attributes.
// Toggle a setting when the user clicks the button.
const toggleSetting = () =>
setAttributes( ( currentAttr ) => ( {
mySetting: ! currentAttr.mySetting,
} ) );
// Append item to the list.
const addListItem = ( newListItem ) =>
setAttributes( ( currentAttr ) => ( {
list: [ ...currentAttr.list, newListItem ],
} ) );
SelectControl: Moved class names to the component root
Theย SelectControlย componentโs class assignments have been adjusted for better consistency with other form components, such asย TextControl. Both the default class name (components-select-control) and any custom class name passed via theย classNameย prop are now applied to the root element of the component, rather than to the internal element.
While this update does not alter the componentโs behavior or visual design, custom CSSCSSCascading Style Sheets. that previously depended on the inner structure may need to be updated.
WordPress 6.9 adds support for registering custom social icons to the Social Icons block. With this update, developers can create plugins to define new services and SVGs, and expose them to both the editor and frontend through block variations and PHPPHPThe web scripting language in which WordPress is primarily architected. WordPress requires PHP 7.4 or higher filters. For styling, matching brand colors can be handled in CSS and loaded for both views.โ
Filters and CSS support synchronization with the frontend output. For more comprehensive references and setup routines, see this Developer Blog post.โ
WordPress 6.9 brings an abundance of quiet improvements to the HTMLHTMLHyperText Markup Language. The semantic scripting language primarily used for outputting content in web browsers.APIAPIAn 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.. Updates in this release mostly represent applications of the HTML API to existing code in CoreCoreCore is the set of software required to run WordPress. The Core Development Team builds WordPress.; these updates increase WordPressโ reliability, improve its security hardening, and reduce maintenance burden on the project.
Major Updates
WP_HTML_Processorโ::โserialize_token() is now public.
The HTML Processorโs serialize_token() method returns a fully-normalized and well-formed representation of the currently-matched token. It was introduced in #62036 for WordPress 6.7 as a private method which performs the heavy-lifting for how the HTML API turns โjunkโ inputs into equivalent well-formed outputs. For example:
Its value outside of WP_HTML_Processor::normalize() became evident, however, particularly in the creation of โserialization builders1โ which make it possible to modify more of the HTML structure than the HTML Processor itself does. In typical HTML API loops, this method can be used to partially extract portions of the document safely:
// Extract the outerHTML of every paragraph element.
$processor = WP_HTML_Processor::create_fragment( $html );
$content = '';
while ( $processor->next_tag( 'P' ) ) {
$content .= $processor->serialize_token();
$depth = $processor->get_current_depth();
while (
$processor->next_token() &&
$processor->get_current_depth() > $depth
) {
$content .= $processor->serialize_token();
}
$content .= $processor->serialize_token();
$content .= "\n\n";
}
WordPress understands JavaScriptJavaScriptJavaScript 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/..dataset properties.
HTML provides a convenient mechanism tying HTML and JavaScript together through the custom data attributes on a tagtagA 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.). These are the attributes starting with data- like dataโwpโinteractive or dataโpostโid and their values are available on the corresponding Element object in JavaScript through the .dataset property:
<span data-order="Carrots please!">
What should we order?
</span>
<script>
document.body.addEventListener(
'click',
event => alert( event.target.dataset.order )
);
</script>
There are endless ways this integration can be used to add a level of dynamism to a site. Unfortunately, how the name of these attributes is transformed looks simpler than it is. For example, the dataโwpโbindโโclass HTML attribute corresponds to the wpBindโClass dataset property.
To prevent confusion, WordPress 6.9 includes two new functions to map between the HTML and JavaScript names: wp_js_dataset_name() indicates what would appear on the .dataset property in a browser while wp_html_custom_data_attribute_name() indicates what name should be used in HTML to produce the .dataset property of a given name. For example:
// What would this HTML attribute name correspond to in JavaScript?
echo wp_js_dataset_name( 'data-one-two--three---four' );
// oneTwo-Three--Four
// What HTML attribute name is necessary to produce the given JavaScript name?
echo wp_html_custom_data_attribute_name( 'postId.guid' );
// data-post-id.guid
No more hard-coding HTML string assertions in unit tests.
WordPress is full of unit tests asserting specific HTML transformations. The expected outputs for these tests are usually hard-coded and sent to $this->assertSame() to compare against the actual outputs from the code under test. Unfortunately this tends to produce a high rate of false positives because of trivialities like adding an attribute in a different order than was expected, using single-quotes around an attribute value rather than double-quotes, leaving extra whitespace or not enough, or using the mistaken self-closer on an <img> or <br> tag.
When two HTML strings produce the same result in a browser they should pass regardless of their insignificant differences. To ease the development of these kinds of tests and to reduce their false-positive rates, WordPress 6.9 introduces a new method on the WP_UnitTestClass base class: $this->assertEqualHTML().
This new test assertion verifies that two strings are equivalent representations of the same normative HTML. They compare HTML strings semantically, provide more useful output than string comparison when they fail to assert, and theyโre even aware of blockBlockBlock 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. semantics.
โ๏ธ Is equivalent html
Time: 00:00.038, Memory: 40.00 MB
OK (1 test, 1 assertion)
This test case would pass since the arguments are two equivalent constructions of the same IMG element. However, a few small changes and it succinctly highlights their differences. The addition of the block comment delimiter is for illustrative purposes only.
โ Is equivalent html
โโHTML markup was not equivalent.โFailed asserting that two strings are identical.โ---ยทExpectedโ+++ยทActualโ@@ @@โ-'BLOCK["core/image"]โ+'BLOCK["core/img"]โ {
โ-ยทยทยทยท"id": 5โ+ยทยทยทยท"id": 6โ }
โ <img>
โ+ยทยทยทยทdata-priority="5"โ loading="lazy"
โ-ยทยทยทยทsrc="puppy.jpg"โ+ยทยทยทยทsrc="puppy.jpg/"โ '
โโต/WordPress-develop/tests/phpunit/includes/abstract-testcase.php:1235โต/WordPress-develop/tests/phpunit/tests/html/equivalentHtmlTest.php:10โด
Time: 00:00.038, Memory: 40.00 MB
The HTML API received minor updates.
The Tag Processorโs constructor will now cast null to an empty string. Similarly, the static creator methods on the HTML Processor will return null instead of an instance of the WP_HTML_Processor class. In each case a _doing_it_wrong() notice will alert developers that these classes expect a string input. This change prevents burying the type errors, which leads to unexpected crashes later on, such as when calling get_updated_html().
When calling set_modifiable_text() on a SCRIPT element, updates are rejected if they contain <script or </script in them. This is a conservative measure to avoid entering the script data double escaped state (personal blogblog(versus network, site)) which is prone to misinterpretation.
Full Changelog
Enhancements
wp_js_dataset_name() and wp_html_custom_data_attribute_name() map between HTML attributes and the .dataset property in JavaScript. [#61501, PR#9953]
The WP_UnitTestClass now contains an assertEqualHTML() method which determines if two strings represent the same normative HTML. [#63527, PR#8882]
Multiple length checks are safely skipped when processing SCRIPT content due to an early minimum-length check. [#63738, PR#9230]
Encoding detection in METAMetaMeta 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. tags is simplified, leading to a minor performance lift. [#63738, PR#9231]
WP_HTML_Processor::serialize_token() is now public, making it easier to mix the full safety of the HTML API with outside code modifying and combining HTML. [#63823, PR#9456]
The Tag Processor and HTML Processor handle invalidinvalidA resolution on the bug tracker (and generally common in software development, sometimes also notabug) that indicates the ticket is not a bug, is a support request, or is generally invalid.null inputs safely. [#63854, PR#9545]
set_modifiable_text() rejects additional contents inside a SCRIPT element when the contents could disturb its normal closing. [#63738, PR#9560]
wp_kses() no longer unescapes escaped numeric character references for users without unfiltered_html, preserving more of the actual entered content in a post or comment. [#63630, PR#9099]
SCRIPT tags are properly closed in the presence of abruptly-closed HTML comments within the contents, and when the closing SCRIPT tagโs tag name is delimited by a form-feed. [#63738, PR#9397]
wp_kses() now allows some previously-missing HTML5 semantic tags and their attributes. [#63786, PR#9379]
set_attribute() directly escapes syntax characters into HTML character references to avoid problems with double-escaping logic. This ensures that all values are represented accurately in the resulting HTML. [#64054, PR#10143]
Core refactors
A number of places in Core were updated to benefit from the HTML API.
Several of the unit tests now rely on assertEqualHTML(), including for block supports, wp_rel_nofollow(), wp_rel_ugc(), wp_kses, post-filtering, media, oEmbed filtering. [#59622, #63694, PR#5486, PR#9251, PR#9255, PR#9257, PR#9258, PR#9259, PR#9264]
get_url_in_content() relies on the Tag Processor to more reliably detect links. Besides improving general HTML parsing, this new version always returns the decoded href attribute, preventing confusion in downstream code. [#63694, PR#9272]
Processing for image blocks in classic themes is now performed via the HTML API rather than with PCREs. [#63694, PR#10218]
Methods to replace innerHTML and outerHTML, wrap an element, unwrap an element, insert elements, and more are possible by scanning through a document and conditionally copying the normalized tokens into an output string. โฉ๏ธ
PHP 8.5 was released on November 20th. Contributors to WordPress have been busy in recent months preparing for this version and weโre happy to report that all issues reported against PHPPHPThe web scripting language in which WordPress is primarily architected. WordPress requires PHP 7.4 or higher 8.5 have been addressed in WordPress 6.9 RC2. Compared to previous PHP releases relatively few changes were required, mostly to address new deprecations and warnings. Take a look at the PHP 8.5 support tracking ticket if youโre interested.
Due to the acknowledgement that WordPress is rarely used in isolation (without any theme or plugins), support is labelled as โbetaBetaA pre-release of software that is given out to a large group of users to trial under real conditions. Beta versions have gone through alpha testing in-house and are generally fairly close in look, feel and function to the final product; however, design changes often occur as part of the process. supportโ until at least 10% of all WordPress sites are running that version or later, as this indicates good compatibility across the wider ecosystem of plugins and themes.
Following the established guidelines, support for any given version of PHP is labelled as โbeta supportโ until at least 10% of all WordPress sites are running that version or later. When WordPress 6.9 ships on December 2nd, support for PHP 8.5 and 8.4 will be labelled as โbeta supportโ. PHP versions 8.3 back to 7.2 are fully supported.
If you discover a bugbugA bug is an error or unexpected result. Performance improvements, code optimization, and are considered enhancements, not defects. After feature freeze, only bugs are dealt with, with regressions (adverse changes from the previous version) being the highest priority. or compatibility issue while running WordPress with PHP 8.5, please create a ticket on Trac.
Even as WordPress CoreCoreCore is the set of software required to run WordPress. The Core Development Team builds WordPress. continues to expand its support for new versions of PHP, support for older versions will remain as-is in WordPress 6.9, staying at PHP 7.2.24 and higher. This will continue to be evaluated and no change will be made until usage numbers show that the impact on users will be minimal.
WordPress continues to encourage all users to run the latest and greatest versions of PHP, including PHP 8.5.
WordPress 6.9 brings extensive accessibilityAccessibilityAccessibility (commonly shortened to a11y) refers to the design of products, devices, services, or environments for people with disabilities. The concept of accessible design ensures both โdirect accessโ (i.e. unassisted) and โindirect accessโ meaning compatibility with a personโs assistive technology (for example, computer screen readers). (https://en.wikipedia.org/wiki/Accessibility) improvements across WordPress CoreCoreCore is the set of software required to run WordPress. The Core Development Team builds WordPress. and GutenbergGutenbergThe Gutenberg project is the new Editor Interface for WordPress. The editor improves the process and experience of creating new content, making writing rich content much simpler. It uses โblocksโ to add richness rather than shortcodes, custom HTML etc. https://wordpress.org/gutenberg/, continuing the goals to meet web content accessibility standards throughout WordPress and make it easier to author accessible content. These updates include changes to administration, customization, login and registration, bundled themes, and the blockBlockBlock 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. editor.
Core
Improvements to WordPress Core include 33 accessibility enhancements and bugbugA bug is an error or unexpected result. Performance improvements, code optimization, and are considered enhancements, not defects. After feature freeze, only bugs are dealt with, with regressions (adverse changes from the previous version) being the highest priority. fixes. Major changes include numerous new or improved screen reader notifications, a complete update of CSSCSSCascading Style Sheets. generated content to ensure no excess content is spoken, and code changes to ensure proper semantics and improved focus management.
Administration
#47101 โ Improve accessibility when deleting terms via AJAX: color contrast & spoken message.
#48655 โ Improve the โAdd-itemโ function in menus (esp. for pages)
#63118 โ Hide โSkip to Toolbarโ shortcut on small screens within adminadmin(and super admin)
#63126 โ Theme preview model and Media library model having issues with Shift/Ctrl + Shift next and previous arrows.
#63449 โ Low color contrast for <code> elements in description text on Settings > General page
#63546 โ Fix unclosed li element in pluginPluginA 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-editor.php
#63603 โ Replace deprecated / non-standard CSS for `speak` and `aural`
#63723 โ On the Add New plugin page, put the Add Plugins screen description above the filterFilterFilters 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. menu
CustomizerCustomizerTool built into WordPress core that hooks into most modern themes. You can use it to preview and modify many of your siteโs appearance settings.
#42078 โ Customize: fix the color hue picker HTMLHTMLHyperText Markup Language. The semantic scripting language primarily used for outputting content in web browsers. and accessibility
#47579 โ Customizer โSelect logoโ and โSelect site iconโ look like drop areas, but are buttons.
#63011 โ Customizer: The back button is not keyboard focusable
#63832 โ Loss of focus when setting or changing the Site Logo or Site Icon in Customizer
Editing
#63460 โ Increase color contrast for embed template
#61959 โ Enhance Support for `popovertarget` and `popover` Attributes in Native Browser Popover APIAPIAn 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.
Login and Registration
#63281 โ Password field has wrong focus on installations
#63286 โ User profile first name, last name, nickname and email fields should have autocomplete attributes for accessibility
#63114 โ No screen reader announcements for upload image errors
#63238 โ Remove `target=โ_blankโ` from Browser Uploader Link
#63239 โ Button focus visibility issue in media upload page
#63571 โ Excessive Spacing Between Right SidebarSidebarA sidebar in WordPress is referred to a widget-ready area used by WordPress themes to display information that is not a part of the main content. It is not always a vertical column on the side. It can be a horizontal rectangle below or above the content area, footer, header, or any where in the theme. Items in Edit Media Screen on Mobile View
#63973 โ Add Media Button missing aria-haspopup and aria-controls
Miscellaneous
#40428 โ Introduce best practices to hide CSS generated content from assistive technologies
#44267 โ Privacy Request List Table: A way to show the time of request when itโs older than 24 hours.
#63030 โ Update CSS for `::-moz-placeholder` color
#63620 โ Remove translator comments when hidden text matches visible text
#63950 โ Tabbing through database upgrade screen shows โWordPressโ text over logo
Bundled Themes
#10219 โ โOlder Entriesโ and โNewer Entriesโ links are wrong when entries displayed in ascending order
#44656 โ Multiple themes: Empty site title leaves empty anchor tagtagA 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.) in headerHeaderThe 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.
#52116 โ Twenty Twenty: Menu + Search can cause a scroll jump on close
#63875 โ Twenty Twenty-Two and Twenty Twenty-Five: <pre> tag overflows container, causing horizontal scroll
Widgets
#63531 โ CategoryCategoryThe 'category' taxonomy lets you group posts / content together that share a common bond. Categories are pre-defined and broad ranging. dropdown does not meet WCAGWCAGWCAG is an acronym for Web Content Accessibility Guidelines. These guidelines are helping make sure the internet is accessible to all people no matter how they would need to access the internet (screen-reader, keyboard only, etc) https://www.w3.org/TR/WCAG21/. 2.2 A on windows and some linux systems
Gutenberg
Changes within Gutenberg include 44 accessibility fixes and enhancements, including the addition of new blocks and the block Notes feature that have undergone accessibility reviews. Numerous fundamental components have had accessibility improvements to ensure that interfaces across the editor are more consistent and understandable.ย
Blocks
#68662 โ Cover: Fix placeholder color options keyboard accessibility
#68909 โ Site Title: Fix logic for โaria-currentโ attribute
#69628 โ Site Title: Prevent saving and rendering a value made of only spaces
#69689 โ Navigation Link, Navigation Submenu: Remove the title attribute controls
#69821 โ Social Icons: Remove custom placeholder state
#70139 โ Button Block: Add HTML Element selection in Advanced settings
#70192 โ Button: Avoid focus loss when unlinking using keyboard
#70210ย โ Columns block: Donโt use ToolsPanelItem for Columns setting
#70730 โ a11yAccessibilityAccessibility (commonly shortened to a11y) refers to the design of products, devices, services, or environments for people with disabilities. The concept of accessible design ensures both โdirect accessโ (i.e. unassisted) and โindirect accessโ meaning compatibility with a personโs assistive technology (for example, computer screen readers). (https://en.wikipedia.org/wiki/Accessibility): Comments Pagination Nav Wrapper
#73177 โ Fix a11y of descriptions and alerts for โInvalidinvalidA resolution on the bug tracker (and generally common in software development, sometimes also notabug) that indicates the ticket is not a bug, is a support request, or is generally invalid.โ Nav Items
Components
#67792 โ Improve the EntitiesSavedStates modal dialog design and labeling
#69011 โ Remove non translatable additional info from font size picker visual label and improve labeling
#69441 โ ARIA: Fix invalid `DropdownMenu` children structure
#68633 โ Global Styles: Prevent Unwanted ItemGroup List Rendering in Border Panel
#68542 โ Button: Update hover styles to account for pressed state for `tertiary button`
#69609 โ ActionModal: Add support for customisable `focusOnMount`
#70591 โ `FormTokenField`: Fix focus lost on tab when `__experimentalExpandOnFocus` is set
#70096 โ Components: Fix label and placeholder handling in `LinkControlSearchInput`
#70660 โ Autocomplete: Prevent text cursor position loss when clicking to insert an item
#70146 โ Color Picker: Improve color picker slider focus styles
Data Views
#67874 โ Display Checkbox by default in dataviews
#69876 โ DataViews: Always show primary action for list layout if hover isnโt supported
#71561 โ DataViews: Custom `empty` elements are no longer wrapped in `<p>` tags to improve accessibility
#72417 โ DataViews: Use Text-Based Links for Primary Actions
#72501 โ Dataviews: Make bulk actions text based.
Editor
#68481 โ Fix CSS classes for the post editor iframeiframeiFrame 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. body.
#68975 โ Close patterns modal on insertion and focus on inserted pattern
When creating and editing menus in the Menus interface, searching for posts and pages has historically used a full text search as a query. This can make some pages difficult to find, if they use a title that primarily uses common words in the site content.
In WordPress 6.9, the search query arguments have been changed to limit searches to only the post title. This is intended to make it easier to find the post youโre looking for in search results.
The query change adds the argument search_columns with the value array( โpost_titleโ ) to the search. The columns correspond to the database columns in the wp_posts table.
To facilitate sites that may need the previous search logic, a filterFilterFilters 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 added to easily modify the search query.
/**
* Filter the menu quick search arguments.
*
* @since 6.9.0
*
* @param array $args {
* ย ย Menu quick search arguments.
*
* ย ย @type booleanย ย ย $no_found_rowsย ย ย ย ย Whether to return found rows data. Default true.
* ย ย @type booleanย ย ย $update_post_meta_cache Whether to update post meta cache. Default false.
* ย ย @type booleanย ย ย $update_post_term_cache Whether to update post term cache. Default false.
* ย ย @type intย ย ย ย ย $posts_per_page ย ย ย ย Number of posts to return. Default 10.
* ย ย @type string ย ย ย $post_typeย ย ย ย ย ย ย Type of post to return.
* ย ย @type string ย ย ย $sย ย ย ย ย ย ย ย ย ย ย Search query.
* ย ย @type arrayย ย ย ย $search_columns ย ย ย ย Which post table columns to query.
* }
*/
$query_args = apply_filters( 'wp_ajax_menu_quick_search_args', $query_args );
To restore the previous behavior, you can unset the additional argument:
Conditional comments were a mechanism to pass scripts and styles for specific versions of IE. The last browser to support conditional comments in any way was IE9, which has been out of support by WordPress since version 4.8. No supported browser uses scripts or styles provided within conditional comments, treating them the same as any other commented code.
The feature has been removed, and using the โconditionalโ argument will throw a deprecation notice (โIE conditional comments are ignored by all supported browsers.โ) if WP_DEBUG is set to true.ย
Any style or script currently loaded using conditional arguments will be ignored, as will any dependencies of those styles or scripts if they are not already required by another script or style.
All bundled themes that used conditional comments have been updated to remove usages of conditional comments and CSSCSSCascading Style Sheets. syntax only used to support Internet Explorer.
CSS and JSJSJavaScript, a web scripting language typically executed in the browser. Often used for advanced user interfaces and behaviors. files that were only required conditionally remain in place as blank files, containing only comments to indicate when the related support was removed.ย
WordPress 6.9 introduces the WP_Block_Processor class โ a new tool inspired by the HTMLHTMLHyperText Markup Language. The semantic scripting language primarily used for outputting content in web browsers.APIAPIAn 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. and designed for efficiently scanning, understanding, and modifying blockBlockBlock 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. structure in HTML documents.
Continue on to learn about this new class, its use-cases, and how you can take advantage of it for more efficient server-side processing.
When a string passed to the esc_url() and esc_url_raw() functions does not include a protocol (https://, http://, etc.), WordPress will prepend http:// to the URLURLA specific web address of a website or web page on the Internet, such as a websiteโs URL www.wordpress.org string before further processing and returning it. This is a reasonable fallback behavior, but there is currently no way to override the default protocol with another (such as https:// for social media profiles).
Starting in WordPress 6.9, the esc_url() and esc_url_raw() functions will now prepend https:// to the URL if it does not already contain a scheme and the first item in the $protocols array is 'https'. The current behavior (prepending http://) will continue when any other value is listed first within the $protocols array.
<?php
/*
* Recommended approach for defaulting to 'https' while maintaining
* backward compatibility.
*
* Example: no protocol with $protocols and 'https' first.
*
* Output:
* - WordPress >= 6.9: 'https://profiles.wordpress.org'
* - WordPress < 6.9: 'http://profiles.wordpress.org'
*/
echo esc_url( 'profiles.wordpress.org', array( 'https', 'http' ) );
By including 'https' first and then 'http' in the array:
WordPress 6.9 would prepend https:// to an incomplete URL.
Older WordPress versions would continue to prepend http://, which remains the default scheme when the $protocols argument is not defined. If the array only contains 'https', esc_url() would return an empty string because http is not included in the list of valid protocols.
Additional examples of output include:
<?php
/*
* Example 1: no protocol included in $url.
*
* Output:
* - WordPress >= 6.9: 'http://profiles.wordpress.org'
* - WordPress < 6.9: 'http://profiles.wordpress.org'
*/
echo esc_url( 'profiles.wordpress.org' );
/*
* Example 2: 'http' protocol included in $url.
*
* Output:
* - WordPress >= 6.9: 'http://profiles.wordpress.org'
* - WordPress < 6.9: 'http://profiles.wordpress.org'
*/
echo esc_url( 'http://profiles.wordpress.org' );
/*
* Example 3: 'https' protocol included in $url.
*
* Output:
* - WordPress >= 6.9: 'https://profiles.wordpress.org'
* - WordPress < 6.9: 'https://profiles.wordpress.org'
*/
echo esc_url( 'https://profiles.wordpress.org' );
/*
* Example 4: no protocol with $protocols and 'http' first.
*
* Output:
* - WordPress >= 6.9: 'http://profiles.wordpress.org'
* - WordPress < 6.9: 'http://profiles.wordpress.org'
*/
echo esc_url( 'profiles.wordpress.org', array( 'http', 'https' ) );
/*
* Example 5: no protocol in $url with $protocols and no 'http'.
*
* Output:
* - WordPress >= 6.9: 'https://profiles.wordpress.org'
* - WordPress < 6.9: ''
*
* Note: if 'http' is not included in the $protocols array,
* the fully escaped URL will not pass the final check that
* a valid, allowed protocol is included.
*/
echo esc_url( 'profiles.wordpress.org', array( 'https' ) );
/*
* Example 6: protocol within $url missing within $protocols.
*
* Output for all:
* - WordPress >= 6.9: ''
* - WordPress < 6.9: ''
*
* Note: if 'http' is not included in the $protocols array,
* the fully escaped URL will not pass the final check that
* a valid, allowed protocol is included.
*/
echo esc_url( 'https://profiles.wordpress.org', array( 'http' ) );
echo esc_url( 'http://profiles.wordpress.org', array( 'https' ) );
echo esc_url( 'mailto:[email protected]', array( 'https', 'http' ) );