Legacy Internet Explorer Code Removed

With WordPress 6.9, numerous legacy features that were used to support Internet Explorer have been removed. All versions of Internet Explorer have been unsupported in WordPress since version 5.8, released in July 2021. These changes continue the process of removing code that existed only to provide support for these browsers.

Removed Support for IE Conditional Scripts and Styles

Conditional comment support was added for scripts in WordPress 4.2, and for styles sometime around version 2.6.0, but was made more easily available in WordPress 3.6.

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.

IE Conditional scripts and styles were removed in core ticket #63821.

Updates to bundled themes

All bundled themes that used conditional comments have been updated to remove usages of conditional comments and CSSCSS Cascading Style Sheets. syntax only used to support Internet Explorer.

CSS and JSJS JavaScript, 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. 

Conditional comments were removed from bundled themes in #58836

IE Compatibility Removals

Compatibility scripts were removed from the Media Elements instantiation process in #63471.

#62128 removed IE-specific hacks and EOT file rules from the Genericons stylesheet for four bundled themes, from Twenty Thirteen to Twenty Sixteen.

Acknowledgements

Props to @jorbin, @westonruter, @jonsurrell and @sabernhardt for reviewing this post.

#6-9, #dev-notes, #dev-notes-6-9

Notes feature in WordPress 6.9

WordPress 6.9 introduces Notes, a new feature that allows you to leave contextual feedback at the 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. level. With notes your team can stay aligned, track changes, and turn feedback into action all in one place. Notes can be resolved, and notes and their replies can be edited or deleted.

Who can use notes?

Because notes can only be created or viewed within the post editor, users must have the edit_post capabilitycapability 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). for that post. By default, this means that:

  • Administrators and Editors can view all notes on all posts.
  • Authors and Contributors can view all notes for posts that they have authored.
  • Subscribers cannot view any notes.

Enabling notes for custom post types

Notes are enabled by default for the post and page built-in post types, but they can be enabled for any custom post typeCustom Post Type WordPress can hold and display many different types of content. A single item of such a content is generally called a post, although post is also a specific post type. Custom Post Types gives your site the ability to have templated posts, to simplify the concept.. When you control the register_post_type() call, this is the preferred way to register support for Notes:

register_post_type( 'book', array(	'label' => 'Books',	'public' => true,	'show_in_rest' => true,	'supports' => array(	'title',	'editor' => array( 'notes' => true ),	'author',	), ) );

When the custom post type is registered through 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, support for Notes can be added by registering support using the following code snippet:

function custom_add_post_type_support() {	$supports = get_all_post_type_supports( 'my-post-type' );	$editor_supports = array( 'notes' => true );	// `add_post_type_support()` overwrites feature sub-properties,	// so they must be explicitly merged.	// See https://core.trac.wordpress.org/ticket/64156.	if (	is_array( $supports['editor'] ) &&	isset( $supports['editor'][0] ) &&	is_array( $supports['editor'][0] )	) {	$editor_supports = array_merge( $editor_supports, $supports['editor'][0] );	}	add_post_type_support( 'my-post-type', 'editor', $editor_supports ); } add_action( 'init', 'custom_add_post_type_support' );

Since notes is a sub-feature of the editor, the code needs to manually merge the notes setting with other editor attributes. Work is underway in https://core.trac.wordpress.org/ticket/64156 to make this easier. Once this bugbug A 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. is fixed, adding support will be simplified:

add_post_type_support( 'my-post-type', 'editor', array(     'notes' => true, ) );

Accessing notes programmatically

Under the hood, Notes are WP_Comments stored in the comments table, and the standard comments APIs can be used to access them by specifying a comment_type of  note

For example, this will retrieve all notes for a given post ID:

$notes = get_comments(	array(	'post_id' => $post_id,	'type' => 'note',	) );

Notes can also be retrieved using the REST APIREST API The 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/.:

$request = new WP_REST_Request( 'GET', '/wp/v2/comments' ); $request->set_param( 'post', $post_id ); $request->set_param( 'type', 'note' ); $response = rest_get_server()->dispatch( $request ); if ( ! is_wp_error( $response ) ) {	$notes = $response->get_data();	foreach ( $notes as $note ) {	// Process each note as needed	} } 

Note status

When a note is added to a block, it:

  • Starts in an “Open” state with a comment_status of 0 (aka hold). 
  • When resolved, changes to a comment_status of 1 (aka approve).
  • When deleted, changes to a status of trash, unless EMPTY_TRASH_DAYS is 0 in which case deleted immediately.

The following snippet will get a list of all unresolved Notes for a given post:

$notes = get_comments(	array(	'post_id' => $post_id,	'type' => 'note',	'status' => 'hold'	) );

Important: Notes must be explicitly requested by specifying either the note or all comment type. Otherwise they are excluded when retrieving comments:

// This only returns comments, 'notes' are automatically excluded. $comments = get_comments( array ( 'post_id' => $post_id ) );

Notification emails

When another user adds a note to a post, the post_author will receive a notification that a note has been added to the post. Notifications are enabled by default and can be controlled at a site level under Settings->Discussions – Email me whenever > Anyone posts a note. Developers can also use the existing comment filters to control notifications. 

For example, to send notifications for notes on pages but not posts, use this snippet:

function only_notify_page_authors_of_notes( $notify, $comment_id ) {	if ( 'note' !== get_comment_type( $comment_id ) {	return $notify;	}	if ( 'page' === get_post_type( $comment_post_ID ) {	return true;	}	return false; } add_filter( 'notify_post_author', 'only_notify_page_authors_of_notes', 10, 2 );

Miscellaneous

Notes permissions

As previously mentioned, only users who can edit a given post are able to add notes. Because notes are an internal/authorized user feature, the normal restrictions that apply to comment posting do not apply to notes, including the flood protection and duplicate prevention. In addition, the pre_comment_approved 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. is not run for notes.

Linking blocks to notes

Top level notes are linked to blocks with a metadata attribute. . When you add a new note to a block, the note ID is stored in block attributes metadata as noteId. Replies are linked to top level notes as children (with their parent set to the top level note)

To get the note ID from a block based on its clientId, you can use this code:

const attributes = getBlockAttributes( clientId ); const noteId = attributes.metadata?.noteId;

Important: The reverse is not true – notes do not contain a link back to their associated block. To identify the block(s) associated with a note, search the blocks for the noteId in metadata. You can use this code snippet:

import { store as blockEditorStore } from '@wordpress/block-editor'; const { getBlockAttributes } = useSelect( blockEditorStore ); const { clientIds } = useSelect( ( select ) => {	const { getClientIdsWithDescendants } = select( blockEditorStore );	return {	clientIds: getClientIdsWithDescendants(),	}; }, [] ); // expectedNoteId is the note we are filtering for. const blocksWithNote = clientIds.reduce( ( results, clientId ) => {	const commentId = getBlockAttributes( clientId )?.metadata?.noteId;	if ( commentId === expectedNoteId ) {	results[ clientId ] = commentId;	}	return results; }, {} );

Known limitations

  • A note can be associated with more than one block. If a block with a note is split or duplicated, the note becomes associated with both blocks. This is primarily due to an underlying limitation in GutenbergGutenberg The 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/ that will be fixed in #29693.
  • A notification is sent for each new non-author note which may not be ideal for all users, especially for cases where a high volume of notes are added to a post will in turn generate a high volume of emails to the post author.
  • Notes do not work outside post content types, for example in templates.
  • All notes are block level, for example this means that they can not reference specific text within a paragraph or text across paragraph blocks.  In-line notes will become available as part of #59445.

What’s next

There are already new features and enhancements planned for Notes in the next release of WordPress (7.0). These features were suggested during the development cycle, but didn’t make it into the 6.9 release.

  • Fragment notes – the ability to leave a note on a part of a block or across blocks.
  • “@” mentions – mention another user in a post and they will receive a notification.
  • Improved notifications – control frequency for notifications, for example receive a daily digest of all new note activity.
  • Improved floating layout for wide screens – shift the floating to sit between the editor frame and the sidebarSidebar A 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..
  • Minified mode – notes display as icons with avatars beside blocks that expand when clicked..
  • Wider availability across the editor including using notes with templates.
  • Real time collaboration with Notes.

You can follow along and contribute to the ongoing effort on the Notes iteration for 7.0 tracking issue.

Have an idea for how to improve Notes? Leave your feedback as a comment below or on the tracking issue linked above.

Props to @jeffpaul, @mamaduka, @wildworks, @annezazu, and @desrosj for review.

#core, #dev-notes, #notes

Preparing the Post Editor for Full iframe Integration

As part of an ongoing effort to modernize the editing experience, WordPress is moving toward running the post editor inside an 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.. This work builds upon the original iframe migrationMigration Moving the code, database and media files for a website site from one server to another. Most typically done when changing hosting companies. in the template editor and introduces new compatibility measures in WordPress 6.9, ahead of the full transition planned for WordPress 7.0.

For background, see the original post.

What’s Changing in WordPress 6.9

Starting with WordPress 6.9, several important updates have been introduced to prepare for this change, which will be completed in WordPress 7.0.

Browser warnings for legacy blocks

To help developers identify legacy blocks, WordPress 6.9 now displays a browser warning when a 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. is registered with apiVersion 2 or lower. This serves as an early signal to update existing blocks before the post editor becomes fully iframed in WordPress 7.0.

When a block is registered with apiVersion 2 or lower, the post editor runs in a non-iframe context as before to maintain backward compatibility. Developers are encouraged to migrate their blocks to apiVersion 3 and test them within the iframe-based editor to ensure full compatibility with future WordPress releases.

See #70783 for more details.

block.jsonJSON JSON, 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. schema now only allows apiVersion: 3

Alongside these compatibility measures, the block.json schema has been updated to only allow apiVersion: 3 for new or updated blocks. Older versions (1 or 2) will no longer pass schema validation.

See #71107 for more details.

Why iframe the Post Editor?

The main goal of iframing the editor is isolation. By loading the editor content within an iframe, styles from the WordPress adminadmin (and super admin) no longer interfere with the editor canvas.

This separation ensures that the editing experience more closely mirrors what users see on the front end.

From a technical perspective, the iframe approach offers several benefits:

  • Admin styles no longer leak into the editor, eliminating the need to reset admin CSSCSS Cascading Style Sheets..
  • Viewport-relative units (vwvh) and media queries now behave naturally within the editor.

The iframed Post Editor will make life easier for block and theme authors by reducing styling conflicts and improving layout accuracy.

Props to @mamaduka for helping review this dev-note.

#6-9, #dev-notes, #dev-notes-6-9

Introducing the streaming block parser in WordPress 6.9

WordPress 6.9 introduces the WP_Block_Processor class — a new tool inspired by the HTMLHTML HyperText Markup Language. The semantic scripting language primarily used for outputting content in web browsers. 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. and designed for efficiently scanning, understanding, and modifying 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. 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.

Continue reading

#6-9, #block-api, #dev-notes, #dev-notes-6-9

Miscellaneous Developer-focused Changes in 6.9

In WordPress 6.9, a handful of small, developer-focused changes were made that deserve to be called out. Let’s take a look!

PHPPHP The web scripting language in which WordPress is primarily architected. WordPress requires PHP 7.4 or higher 8.5 New Function Polyfills

The upcoming 8.5 release of PHP includes 2 new functions that can be used to retrieve values from an array:

To encourage more modern PHP practices, polyfills of these functions have been added to WordPress CoreCore Core is the set of software required to run WordPress. The Core Development Team builds WordPress. so that they can be used safely without needing to include function_exists() checks.

For more information, see #63853.

Improved Feed Caching on 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 Installs

When RSS feeds are fetched using fetch_feed(), they are currently cached using the (get|set|delete)_transient() functions. On multisite installs, single site transients cannot be shared between sites, but site transients managed through (get|set|delete)_site_transient() can.

In WordPress 6.9, fetch_feed() will now store feeds as site transients instead. This allows a feed to be fetched and stored once for all sites on a multisite install, which improves the overall performance of feeds and reduces the amount of data cached in the database (or persistent object cache, if configured). One example where this is beneficial is the WordPress News blogblog (versus network, site) feed, which is fetched and displayed in a 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. on the dashboard for every user.

See #63719 for more details.

External Library Updates

The following external libraries bundled in WordPress are being updated in the 6.9 release:

  • ID3 has been updated to include two changes from the upstream repository that address PHP 8.5 compatibility issues (see #64051). Once a new version containing these two changes has officially been tagged, the library will be updated in full (see #64253).
  • PHPMailer has been updated from 6.9.1 to 6.11.1 (see #63811, #64052, #64055).
  • SimplePie has been updated from 1.8.0 to 1.9.0 (see #63717, #63961).
  • Sodium Compat has been updated from 1.21.1 to 1.23.0 (see #64008, #64079).

New Classic Themes Template for TaxonomyTaxonomy A taxonomy is a way to group things together. In WordPress, some common taxonomies are category, link, tag, or post format. https://codex.wordpress.org/Taxonomies#Default_Taxonomies. Terms

In classic themes, you can now use term IDs when creating taxonomy templates. With taxonomy-$taxonomy-{$term->term_id}.php templates, you don’t need to worry about templates breaking if you rename the Oakland Athletics term to Athletics the way you do if you use slug based template names. 

See #35326 for more information.

Better Handling of Transparent PNGs

WordPress 6.8 introduced a regressionregression A software bug that breaks or degrades something that previously worked. Regressions are often treated as critical bugs or blockers. Recent regressions may be given higher priorities. A "3.6 regression" would be a bug in 3.6 that worked as intended in 3.5. that could degrade the quality of PNG images with transparency when processed with Imagick. This was fixed in [60667] by improving the detection of the various types of transparent PNG files and handles them each appropriately.

See #63448 for more information.

New HooksHooks In 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.

WordPress 6.9 will see 21 new action and 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. hooks added. Some will be detailed in a separate developer note, but they’re all listed below for easy reference.

New Action Hooks

There are 8 new action hooks being introduced in 6.9: 

New Filter Hooks

There are 13 new filters being introduced in 6.9:

A Final Farewell to Flash

While Flash was officially retired at the end of 2020, there were a few artifacts remaining within WordPress in the form of SWFObject and SWFUpload. These were removed in [60281] and will no longer ship in new versions of WordPress going forward.

For more information, see #52699 on TracTrac An open source project by Edgewall Software that serves as a bug tracker and project management tool for WordPress..

Props @adamsilverstein for helping to draft the post, and @jorbin for peer-review.

#6-9, #dev-notes

Abilities API in WordPress 6.9

WordPress 6.9 introduces the Abilities 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., a new foundational system that enables plugins, themes, and WordPress coreCore Core is the set of software required to run WordPress. The Core Development Team builds WordPress. to register and expose their capabilitiescapability 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). in a standardized, machine-readable format. This API creates a unified registry of functionality that can be discovered, validated, and executed consistently across different contexts, including PHPPHP The web scripting language in which WordPress is primarily architected. WordPress requires PHP 7.4 or higher, REST APIREST API The 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/. endpoints, and future AI-powered integrations.

The Abilities API is part of the broader AI Building Blocks for WordPress initiative, providing the groundwork for AI agents, automation tools, and developers to understand and interact with WordPress functionality in a predictable manner.

What is the Abilities API?

An ability is a self-contained unit of functionality with defined inputs, outputs, permissions, and execution logic. By registering abilities through the Abilities API, developers can:

  • Create discoverable functionality with standardized interfaces
  • Define permission checks and execution callbacks
  • Organize abilities into logical categories
  • Validate inputs and outputs
  • Automatically expose abilities through REST API endpoints

Rather than burying functionality in isolated functions or custom AJAX handlers, abilities are registered in a central registry that makes them accessible through multiple interfaces.

Core Components

The Abilities API introduces three main components to WordPress 6.9:

1. PHP API

A set of functions for registering, managing, and executing abilities:

Ability Management:

  • wp_register_ability() – Register a new ability
  • wp_unregister_ability() – Unregister an ability
  • wp_has_ability() – Check if an ability is registered
  • wp_get_ability() – Retrieve a registered ability
  • wp_get_abilities() – Retrieve all registered abilities

Ability CategoryCategory The 'category' taxonomy lets you group posts / content together that share a common bond. Categories are pre-defined and broad ranging. Management:

  • wp_register_ability_category() – Register an ability category
  • wp_unregister_ability_category() – Unregister an ability category
  • wp_has_ability_category() – Check if an ability category is registered
  • wp_get_ability_category() – Retrieve a registered ability category
  • wp_get_ability_categories() – Retrieve all registered ability categories

2. REST API Endpoints

When enabled, the Abilities API can automatically expose registered abilities through REST API endpoints under the wp-abilities/v1 namespace:

  • GET /wp-abilities/v1/categories – List all ability categories
  • GET /wp-abilities/v1/categories/{slug} – Get a single ability category
  • GET /wp-abilities/v1/abilities – List all abilities
  • GET /wp-abilities/v1/abilities/{name} – Get a single ability
  • GET|POST|DELETE /wp-abilities/v1/abilities/{name}/run – Execute an ability

3. HooksHooks In 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.

New action hooks for integrating with the Abilities API:

Actions:

  • wp_abilities_api_categories_init – Fired when the ability categories registry is initialized (register categories here)
  • wp_abilities_api_init – Fired when the abilities registry is initialized (register abilities here)
  • wp_before_execute_ability – Fired before an ability executes
  • wp_after_execute_ability – Fired after an ability finishes executing

Filters:

  • wp_register_ability_category_args – Filters ability category arguments before registration
  • wp_register_ability_args – Filters ability arguments before registration

Registering Abilities

Abilities must be registered on the wp_abilities_api_init action hook. Attempting to register abilities outside of this hook will trigger a _doing_it_wrong() notice, and the Ability registration will fail.

Basic Example

Here’s a complete example of registering an ability category and an ability:

 <?php add_action( 'wp_abilities_api_categories_init', 'my_plugin_register_ability_categories' ); /** * Register ability categories. */ function my_plugin_register_ability_categories() { wp_register_ability_category( 'content-management', array( 'label' => __( 'Content Management', 'my-plugin' ), 'description' => __( 'Abilities for managing and organizing content.', 'my-plugin' ), ) ); } add_action( 'wp_abilities_api_init', 'my_plugin_register_abilities' ); /** * Register abilities. */ function my_plugin_register_abilities() { wp_register_ability( 'my-plugin/get-post-count', array( 'label' => __( 'Get Post Count', 'my-plugin' ), 'description' => __( 'Retrieves the total number of published posts.', 'my-plugin' ), 'category' => 'content-management', 'input_schema' => array( 'type' => 'string', 'description' => __( 'The post type to count.', 'my-plugin' ), 'default' => 'post', ), 'output_schema' => array( 'type' => 'integer', 'description' => __( 'The number of published posts.', 'my-plugin' ), ), 'execute_callback' => 'my_plugin_get_post_count', 'permission_callback' => function() { return current_user_can( 'read' ); }, ) ); } /** * Execute callback for get-post-count ability. */ function my_plugin_get_post_count( $input ) { $post_type = $input ?? 'post'; $count = wp_count_posts( $post_type ); return (int) $count->publish; } 

More Complex Example

Here’s an example with more advanced input and output schemas, input validation, and error handling:

 <?php add_action( 'wp_abilities_api_init', 'my_plugin_register_text_analysis_ability' ); /** * Register a text analysis ability. */ function my_plugin_register_text_analysis_ability() { wp_register_ability( 'my-plugin/analyze-text', array( 'label' => __( 'Analyze Text', 'my-plugin' ), 'description' => __( 'Performs sentiment analysis on provided text.', 'my-plugin' ), 'category' => 'text-processing', 'input_schema' => array( 'type' => 'object', 'properties' => array( 'text' => array( 'type' => 'string', 'description' => __( 'The text to analyze.', 'my-plugin' ), 'minLength' => 1, 'maxLength' => 5000, ), 'options' => array( 'type' => 'object', 'properties' => array( 'include_keywords' => array( 'type' => 'boolean', 'description' => __( 'Whether to extract keywords.', 'my-plugin' ), 'default' => false, ), ), ), ), 'required' => array( 'text' ), ), 'output_schema' => array( 'type' => 'object', 'properties' => array( 'sentiment' => array( 'type' => 'string', 'enum' => array( 'positive', 'neutral', 'negative' ), 'description' => __( 'The detected sentiment.', 'my-plugin' ), ), 'confidence' => array( 'type' => 'number', 'minimum' => 0, 'maximum' => 1, 'description' => __( 'Confidence score for the sentiment.', 'my-plugin' ), ), 'keywords' => array( 'type' => 'array', 'items' => array( 'type' => 'string', ), 'description' => __( 'Extracted keywords (if requested).', 'my-plugin' ), ), ), ), 'execute_callback' => 'my_plugin_analyze_text', 'permission_callback' => function() { return current_user_can( 'edit_posts' ); }, ) ); } /** * Execute callback for analyze-text ability. * * @param $input * @return array */ function my_plugin_analyze_text( $input ) { $text = $input['text']; $include_keywords = $input['options']['include_keywords'] ?? false; // Perform analysis (simplified example) $sentiment = 'neutral'; $confidence = 0.75; $result = array( 'sentiment' => $sentiment, 'confidence' => $confidence, ); if ( $include_keywords ) { $result['keywords'] = array( 'example', 'keyword' ); } return $result; } 

Ability Naming Conventions

Ability names should follow these practices:

  • Use namespaced names to prevent conflicts (e.g., my-plugin/my-ability)
  • Use only lowercase alphanumeric characters, dashes, and forward slashes
  • Use descriptive, action-oriented names (e.g., process-payment, generate-report)
  • The format should be namespace/ability-name

Categories

Abilities must be assigned to a category. Categories provide better discoverability and help organize related abilities. Categories must be registered before the abilities that reference them using the wp_abilities_api_categories_init hook.

JSONJSON JSON, 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. Schema Validation

The Abilities API uses JSON Schema for input and output validation. WordPress implements a validator based on a subset of JSON Schema Version 4. The schemas serve two purposes:

  1. Automatic validation of data passed to and returned from abilities
  2. Self-documenting API contracts for developers

Defining schemas is mandatory when there is a value to pass or return.

Using REST API Endpoints

Developers can also enable Abilities to support the default REST API endpoints. This is possible by setting the meta.show_in_rest argument to true when registering an ability.

 wp_register_ability( 'my-plugin/get-post-count', array( 'label' => __( 'Get Post Count', 'my-plugin' ), 'description' => __( 'Retrieves the total number of published posts.', 'my-plugin' ), 'category' => 'content-management', 'input_schema' => array( 'type' => 'string', 'description' => __( 'The post type to count.', 'my-plugin' ), 'default' => 'post', ), 'output_schema' => array( 'type' => 'integer', 'description' => __( 'The number of published posts.', 'my-plugin' ), ), 'execute_callback' => 'my_plugin_get_post_count', 'permission_callback' => function() { return current_user_can( 'read' ); }, 'meta' => array( 'show_in_rest' => true, ) ) ); 

Access to all Abilities REST API endpoints requires an authenticated user. The Abilities API supports all WordPress REST API authentication methods:

  • Cookie authentication (same-origin requests)
  • Application passwords (recommended for external access)
  • Custom authentication plugins

Once enabled, it’s possible to list, fetch, and execute Abilities via the REST API endpoints:

List All Abilities:

 curl -u 'USERNAME:APPLICATION_PASSWORD' \ https://example.com/wp-json/wp-abilities/v1/abilities 

Get a Single Ability:

 curl -u 'USERNAME:APPLICATION_PASSWORD' \ https://example.com/wp-json/wp-abilities/v1/abilities/my-plugin/get-post-count 

Execute an Ability:

 curl -u 'USERNAME:APPLICATION_PASSWORD' \ -X POST https://example.com/wp-json/wp-abilities/v1/abilities/my-plugin/get-post-count/run \ -H "Content-Type: application/json" \ -d '{"input": {"post_type": "page"}}' 

The API automatically validates the input against the ability’s input schema, checks permissions via the ability’s permission callback, executes the ability, validates the output against the ability’s output schema, and returns the result as JSON.

Checking and Retrieving Abilities

You can check if an ability exists and retrieve it programmatically:

 <?php // Check if an ability is registered if ( wp_has_ability( 'my-plugin/get-post-count' ) ) { // Get the ability object $ability = wp_get_ability( 'my-plugin/get-post-count' ); // Access ability properties echo $ability->get_label(); echo $ability->get_description(); } // Get all registered abilities $all_abilities = wp_get_abilities(); foreach ( $all_abilities as $ability ) { echo $ability->get_name(); } 

Error Handling

Abilities should handle errors gracefully by returning WP_Error objects:

 <?php function my_plugin_delete_post( $input ) { $post_id = $input['post_id']; if ( ! get_post( $post_id ) ) { return new WP_Error( 'post_not_found', __( 'The specified post does not exist.', 'my-plugin' ), ); } $result = wp_delete_post( $post_id, true ); if ( ! $result ) { return new WP_Error( 'deletion_failed', __( 'Failed to delete the post.', 'my-plugin' ), ); } return array( 'success' => true, 'post_id' => $post_id, ); } 

Backward Compatibility

The Abilities API is a new feature in WordPress 6.9 and does not affect existing WordPress functionality. Plugins and themes can adopt the API incrementally without breaking existing code.

For developers who want to support both WordPress 6.9+ and earlier versions, check if the API functions exist before using them:

 <?php if ( function_exists( 'wp_register_ability' ) ) { add_action( 'wp_abilities_api_init', 'my_plugin_register_abilities' ); } 

Or

 if ( class_exists( 'WP_Ability' ) ) { add_action( 'wp_abilities_api_init', 'my_plugin_register_abilities' );} 

Further Resources

Props to @gziolo for pre-publish review.

#abilities-api, #6-9, #dev-notes, #dev-notes-6-9

Consistent Cache Keys for Query Groups in WordPress 6.9

Query caches have historically used the last changed timestamp as a salt. While this has proven effective for most sites, it leads to an excessive number of caches which can be problematic on high-traffic and heavily updated sites. WordPress 6.9 introduces changes to how cache keys are created in order to ensure efficient use of object caches and help caches clean up after themselves.

These changes are compatible with existing implementations of persistent caching drop-ins. Vendors are not required to make any changes to their code to support these features in WordPress 6.9. As with other caching functions, the functions are pluggable should vendors wish to optimize for their particular caching implementation. These new functions are:

  • wp_cache_get_salted( string $cache_key, string $group, string|string[] $salt ): mixed
  • wp_cache_set_salted( string $cache_key, mixed $data, string $group, string|string[] $salt, int $expire = 0 ): bool
  • wp_cache_get_multiple_salted( string[] $cache_keys, string $group, string|string[] $salt ): mixed[]
  • wp_cache_set_multiple_salted( mixed[] $data, string $group, string|string[] $salt, int $expire = 0 ): bool[]

What Behavior is Changing

Previous behavior in WordPress 6.8 and earlier:

  • A post object is saved
  • WordPress stores the last changed time of the posts table
  • WP Query is called
  • WordPress caches the database query using a key containing the last changed time
  • Another post is saved, updating the posts table’s last changed time
  • The previous cache becomes unreachable
  • WP Query is called
  • WordPress does not see a cached query
  • WordPress caches the database query using a new key containing the updated last changed time

New behavior in WordPress 6.9

  • A post object is saved
  • WordPress stores the last changed time of the posts table
  • WP Query is called
  • WordPress caches the database query alongside the last changed time
  • Another post is saved, updating the posts table’s last changed time
  • WP Query is called
  • WordPress hits the previously generated cache
  • WordPress uses the last changed value to determine if the cache is up-to-date
  • WordPress replaces the previously generated cache with the new results

While both operations perform two cache lookups, the new behavior re-uses the existing cache key and does an in memory comparison of the last changed time. This prevents the cache from containing unreachable cache keys.

The same change in behavior applies to other Query classes such as term queries, comment queries, user queries, etc.

Checking/setting query caches directly

Broadly, the caches affected are in the following cache groups:

  • comment-queries
  • network-queries
  • post-queries
  • site-queries
  • term-queries
  • user-queries
The following specific cache keys are affected and will now be different.  If you have been directly checking or setting caches that start with the following keys, you will need to adjust your code.
  • get_comments
  • get_comment_child_ids
  • get_network_ids
  • comment_feed
  • wp_query
  • get_sites
  • wp_get_archives
  • adjacent_post
  • get_page_by_path
  • find_post_by_old_slug
  • get_objects_in_term
  • count_user_postscount_user_posts

Additionally, keys that are generated by WP_User_Query:generate_cache_key and WP_Term_Query::generate_cache_key are affected as well. However, these keys are md5 hashes and thus are less likely to be used directly.

When using the new wp_cache_*_salted functions and passing in an array of salts, the order of items in the array must be consistent. If you are using the changed caches in coreCore Core is the set of software required to run WordPress. The Core Development Team builds WordPress., please review the order in core and use the same order to ensure cache hits. 

If you need to support multiple versions of WordPress when updating your code, you can use code that looks similar to:

if (function_exists( 'wp_cache_get_salted` ) ){   $data = wp_cache_get_salted( $cache_key, 'comment-queries', $last_changed ); } else {   // your current code here }

See [60697] for specific examples of how core has been updated and [60941] for an example of how these new functions help cache previously uncached code. 

On Upgrade to WordPress 6.9

With the update to cache keys, it’s expected that you may see a short term increase in cache misses upon upgrade. You may wish to preemptively evict the old cache keys in order to prevent stale entries from sticking around, potentially leading to unnecessary evictions based on your cache policies.

For more information, please see #59592.

Props to @desrosj@peterwilsoncc, and @spacedmonkey for review. Props to @spacedmonkey for comments on the ticketticket Created for both bug reports and feature development on the bug tracker. that could be easily reused here.

#6-9, #cache-api, #dev-notes, #dev-notes-6-9

Modernizing UTF-8 support in WordPress 6.9

A number of changes in WordPress 6.9 are coming which modernize WordPress’ text encoding and UTF-8 handling. These improvements establish more reliable and consistent text processing across WordPress’ widely-supported environments, benefiting plugins and themes that handle international content, emoji, diacritics, and more.

TL;DR

  • UTF-8 handling in WordPress no longer depends on the running environment thanks to a new fallback pipeline written in pure PHPPHP The web scripting language in which WordPress is primarily architected. WordPress requires PHP 7.4 or higher. This means that code which works on your development server will work everywhere it’s deployedDeploy Launching code from a local development environment to the production web server, so that it's available to visitors..
  • Some legacy functions are misleading in their design and are difficult to use properly; these have been deprecated and replaced by more specific and purpose-built alternatives.
    • Prefer wp_is_valid_utf8() instead of seems_utf8().
    • Prefer wp_scrub_utf8() instead of wp_check_invalid_utf8().
    • Prefer mb_convert_encoding() instead of utf8_encode() and utf8_decode().

While humbler than some of the other exciting features in this release, this overhaul of UTF-8 support reflects WordPress’ commitment to a stable and trustworthy internationalized experience.

Read on to learn more about these changes and how to avoid common mistakes and misunderstandings around strings and text.

Continue reading

#6-9, #dev-notes, #dev-notes-6-9, #formatting, #utf8

WordPress 6.9 Frontend Performance Field Guide

This post is the latest in a series of updates focused on the performance improvements of major releases (see 6.8, 6.7, 6.6, 6.5, 6.4, 6.3, and 6.2).

WordPress 6.9 is the second and final major releasemajor release A release, identified by the first two numbers (3.6), which is the focus of a full release cycle and feature development. WordPress uses decimaling count for major release versions, so 2.8, 2.9, 3.0, and 3.1 are sequential and comparable in scope. of 2025. It includes numerous improvements to the performance of loading pages on the frontend:

  • Scripts: improve script loading performance by adding support for fetchpriority, printing script modules in the footer, and optimizing the emoji detection script.
  • Styles: optimize loading of stylesheets by loading 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. styles on demand in classic themes, omitting styles for hidden blocks, increasing the inline style limit from 20K to 40K, and inlining minified stylesheets in block themes.
  • Introduce the template enhancementenhancement Enhancements are simple improvements to WordPress, such as the addition of a hook, a new feature, or an improvement to an existing feature. output buffer to implement optimizations previously impossible (such as the aforementioned on-demand style loading in classic themes).
  • More: spawn WP Cron at shutdown, eliminate layout shifts in the Video block, fix RSS feedRSS Feed RSS is an acronym for Real Simple Syndication which is a type of web feed which allows users to access updates to online content in a standardized, computer-readable format. This is the feed. caching, and so on.

The performance changes in this release include 38 Trac tickets (26 enhancements, 11 defects, 1 task) and 31 Gutenberg PRs, although this post does not describe improvements to the performance of the editor nor the database query and caching optimizations. This post highlights the key changes to the frontend for site visitors, as well as looking at their impact in terms of web vitals metrics, such as TTFB, FCP, and LCP.

Continue reading

#6-9, #core, #core-performance, #css, #dev-notes, #dev-notes-6-9, #javascript, #performance

Block Bindings improvements in WordPress 6.9

For WordPress users.

The 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. Bindings user interface has been upgraded to improve how different data sources are displayed in the editor.
Users can now easily switch between sources, as well as bind and unbind attributes with a single click.

For WordPress developers.

On the server

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.block_bindings_supported_attributes_{$block_type}, allows developers to customize which of a block’s attributes can be connected to a Block Bindings source.

On the editor

Developers can now register custom sources in the editor UIUI User interface by adding a getFieldsList method to their source registration function.

This function must return an array of objects with the following properties:

  • label (string): Defines the label shown in the dropdown selector. Defaults to the source label if not provided.
  • type (string): Defines the attribute value type. It must match the attribute type it binds to; otherwise, it won’t appear in the UI.Example: An id attribute that accepts only numbers should only display fields that return numeric values.
  • args (object): Defines the source arguments that are applied when a user selects the field from the dropdown.

This is an example that can be tried directly in the console from the Block Editor:

 wp.blocks.registerBlockBindingsSource({	name: 'state-word/haikus',	label: 'Haikus',	useContext: [ 'postId', 'postType' ],	getValues: ( { bindings } ) => { // this getValues assumes you're on a paragraph	if ( bindings.content?.args?.haiku === 'one' ) {	return {	content:	'Six point nine arrives,\nBlock bindings bloom like spring flowers,\nEditors rejoice.',	};	}	if ( bindings.content?.args?.haiku === 'two' ) {	return {	content:	'New features unfold,\nPatterns dance with dynamic grace,\nWordPress dreams take flight.',	};	}	if ( bindings.content?.args?.haiku === 'three' ) {	return {	content:	"December's gift shines,\nSix nine brings the future near,\nCreators build more.",	};	}	return {	content: bindings.content,	};	},	getFieldsList() {	return [	{	label: 'First Haiku',	type: 'string',	args: {	haiku: 'one',	},	},	{	label: 'Second Haiku',	type: 'string',	args: {	haiku: 'two',	},	},	{	label: 'Third Haiku',	type: 'string',	args: {	haiku: 'three',	},	},	];	}, } ); 

After executing the code above, when inserting a paragraph, a new UI selector for the Block Binding should be available for the content attribute of the paragraph.

Additional Information

More information can be found on the related tickets, changesets, and pull requests:

  • TracTrac An open source project by Edgewall Software that serves as a bug tracker and project management tool for WordPress. ticketticket Created for both bug reports and feature development on the bug tracker.: #64030
  • Changeset: [60807]
  • gutenberg repository pull request: PR-71820
  • gutenberg pull request the idea originated from: PR-70975

Props: @bernhard-reiter and @cbravobernal for implementation. @juanmaguitar for peer review and providing examples.

#6-9, #block-bindings, #dev-notes, #dev-notes-6-9