Title: dev-notes – Make WordPress Core

---

#  Tag Archives: dev-notes

 [  ](https://profiles.wordpress.org/czarate/) [Chris Zarate](https://profiles.wordpress.org/czarate/)
10:45 pm _on_ April 1, 2026     
Tags: [7-0 ( 53 )](https://make.wordpress.org/core/tag/7-0/),
dev-notes, [dev-notes-7-0 ( 19 )](https://make.wordpress.org/core/tag/dev-notes-7-0/),
feature-real-time-collaboration, [hosting ( 44 )](https://make.wordpress.org/core/tag/hosting/)

# 󠀁[Building a custom sync provider for real-time collaboration](https://make.wordpress.org/core/2026/04/01/building-a-custom-sync-provider-for-real-time-collaboration/)󠁿

WordPress 7.0 will introduce real-time collaboration in 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. editor. Out of
the box, the editor syncs changes between peers using an HTTPHTTP HTTP is an acronym
for Hyper Text Transfer Protocol. HTTP is the underlying protocol used by the World
Wide Web and this protocol defines how messages are formatted and transmitted, and
what actions Web servers and browsers should take in response to various commands.
polling provider. However, an HTTP polling transport isn’t the only option and it
may not be the best fit for your infrastructure, especially if you are a WordPress
hosting provider.

The `sync.providers` client-side filterFilter Filters are one of the two types of
Hooks [https://codex.wordpress.org/Plugin_API/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. proposed for WordPress 7.0 lets you replace the default transport with your
own. This post walks through why you’d want to use one, what a provider does, and
how to build one.

## Why build a custom provider?

The default HTTP polling provider is designed to work on any WordPress installation.
It batches document and awareness updates into periodic HTTP requests: every four
seconds when editing alone, every second when collaborators are present. ([These values are filterable.](https://github.com/WordPress/gutenberg/blob/41bef30bf359d085236d447ee98b7d83c1664658/packages/sync/src/providers/http-polling/config.ts#L10-L18))

It works reliably, but there can be good reasons to swap it out:

 * **Lower latency.** Transports such as WebSockets deliver updates as they happen,
   not on a polling interval. For sites doing heavy collaborative editing, the difference
   can be noticeable.
 * **Reduced server load.** Polling generates requests even when nothing has changed.
   A push-based transport only sends data when needed.
 * **Infrastructure alignment.** If you already run WebSocket servers or other real-
   time transport, you can benefit from using familiar infrastructure with WordPress.

These benefits come with a substantial overhead. Building a custom provider is not
trivial. It will require custom code. Most likely, it will also involve deployingDeploy
Launching code from a local development environment to the production web server,
so that it's available to visitors. and maintaining server resources.

## What a sync provider does

Real-time collaboration in WordPress is powered by [Yjs](https://yjs.dev/), a Conflictconflict
A conflict occurs when a patch changes code that was modified after the patch was
created. These patches are considered _stale_, and will require a _refresh_ of the
changes before it can be applied, or the conflicts will need to be _resolved_.-free
Replicated Data Type (CRDT) library. WordPress content is represented by Yjs documents;
syncing happens by exchanging updates to those documents.

The sync provider is the transport layer. It facilitates the exchange of Yjs document
updates between peers.

Concretely, a provider needs to:

 1. **Receive local Yjs document updates** and send them to remote peers.
 2. **Receive remote updates** and apply them to the local Yjs document.
 3. **Report connection status** so the editor UIUI User interface can show whether
    the user is connected.

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/](https://wordpress.org/gutenberg/)’
s sync manager orchestrates the syncing process. It creates a sync provider for 
each Yjs document that will be synced. Therefore, supplying a custom sync provider
means supplying a provider creator function. A provider creator is an async function
following this example:

    ```wp-block-code
    async function myProviderCreator( options ) {
        const objectType = options.objectType; // e.g., "postType/post"
        const objectId   = options.objectId;   // e.g., "123"
        const ydoc       = options.ydoc;       // Yjs document
        const awareness  = options.awareness;  // Yjs awareness

        // Create provider.
        const room     = `${ objectType }:${ objectId }`;
        const provider = new MyYjsProvider( ydoc, awareness, room );

        // Connect.
        provider.connect();

        return {
            destroy: () => provider.destroy(),
            on: ( event, callback ) => provider.on( event, callback ),
         };
    }
    ```

Note that the returned object has two function properties that the provider must
implement:

 * **destroy()**: The sync manager will call this function when it is time to close
   connections, remove listeners, and free resources.
 * **on()**: This function allows the sync manager to subscribe to connection state
   changes. Emit `{ status: 'connecting' }`, `{ status: 'connected' }`, or `{ status:'
   disconnected', error?: ConnectionError }` as appropriate.
    - A disconnected event can be accompanied by an error. Using specific error 
      codes allows the editor to give specific feedback to the user. See [the list of error codes](https://github.com/WordPress/gutenberg/blob/41bef30bf359d085236d447ee98b7d83c1664658/packages/sync/src/errors.ts#L2-L6)
      and resulting messaging.

## Existing Yjs providers

You don’t have to build a sync provider from scratch. Yjs has [a provider ecosystem](https://docs.yjs.dev/ecosystem/connection-provider)
and several existing libraries can handle the heavy lifting. 

[y-websocket](https://github.com/yjs/y-websocket) is the most widely used Yjs provider
and has been deployedDeploy Launching code from a local development environment 
to the production web server, so that it's available to visitors. by [WordPress VIP](https://wpvip.com/)
and other WordPress hosts. It includes both a client and a simple Node.js server.

**Note:** [y-webrtc](https://github.com/yjs/y-webrtc) is nominally a peer-to-peer
provider that syncs via WebRTC, but in practice it [requires centralized servers to reliably connect peers](https://webrtc.org/getting-started/peer-connections)
with each other. It is not recommended unless you are willing to invest in those
servers. 

### Minimal client example with y-websocket

Wrapping a Yjs provider in a ProviderCreator function is straightforward, as seen
in the following example. However, note that this example is missing essential authorization
checks (discussed in the next section):

    ```javascript
    import { addFilter } from '@wordpress/hooks';
    import { WebsocketProvider } from 'y-websocket';

    addFilter( 'sync.providers', 'my-plugin/websocket', () => {
        return [
            async ( { objectType, objectId, ydoc, awareness } ) => {
                const roomName = `${ objectType }-${ objectId ?? 'collection' }`;
                const provider = new WebsocketProvider(
                    'wss://my-sync-server.example.com',
                    roomName,
                    ydoc,
                    { awareness }
                );

                return {
                    destroy: () => provider.destroy(),
                    on: ( event, callback ) => provider.on( event, callback ),
                };
            },
        ];
    } );
    ```

This code replaces the default HTTP polling provider entirely. The filter callback
ignores the incoming providerCreators array and returns a new array containing a
single WebSocket-based provider creator.

The WebSocket server (`wss://my-sync-server.example.com` in the example above) must
be configured and deployed separately. The [y-websocket-server](https://github.com/yjs/y-websocket-server)
library is the server companion to y-websocket.

## Authorization and security

A custom sync provider connects to infrastructure that you own and operate, e.g.,
a WebSocket server. Because that infrastructure lives outside of WordPress, WordPress
can’t authorize requests to it on your behalf.

Securing the connection between the editor and your sync server is your responsibility—
a critical one. Without authorization checks, any user could connect to your WebSocket
server and participate in a collaborative session with your WordPress users. 

### Token-based auth

A common pattern is to issue short-lived tokens via a WordPress 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/](https://developer.wordpress.org/rest-api/)
endpoint, then pass the token when opening the WebSocket connection. The tokens 
assert that the user has permission to collaborate on a specific entity.

Here’s a simplified example of how the [WPVIP Real-Time Collaboration plugin](https://github.com/Automattic/vip-real-time-collaboration)
handles it:

    ```javascript
    // Fetch a short-lived token from a WordPress REST endpoint.
    // This endpoint is provided by your plugin. Tokens encode the
    // type and ID of the entity being edited, as well as the current
    // WordPress user ID.
    const data = await apiFetch( {
        path: '/my-plugin/v1/sync/auth',
        method: 'GET',
        data: { objectType, objectId },
    } );

    // Pass the token as a query parameter when connecting.
    provider.params = { auth: data.token };
    provider.connect();
    ```

### Key considerations

 * **Validate on the server.** Never trust the client. The sync server should verify
   the token on every connection request. The token should encode information about
   the user, the entity being edited, and which actions are authorized. The sync
   server should validate each assertion and reject unauthorized connections before
   applying any document updates.
 * **Authorize per-document.** It’s worth restating: Don’t just authenticate the
   user, additionally verify they have permission to edit the specific post or entity
   being synced. Your WebSocket server should validate this on every connection.
 * **Rotate tokens.** WebSocket connections are long-lived. Use short-lived tokens
   and re-authenticate on reconnect so that revoked permissions take effect promptly.
 * **Handle disconnects gracefully**. When authorization fails or a token is invalidinvalid
   A 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., emit a `{ status: 'disconnected', error }`
   event so the editor can inform the user. The WPVIP 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/](https://wordpress.org/plugins/) or can be cost-
   based plugin from a third-party. maps WebSocket close codes to specific error
   types to give users actionable feedback.

The [WPVIP Real-Time Collaboration plugin](https://github.com/Automattic/vip-real-time-collaboration)
is a functional and secure example using WebSockets. It’s open sourceOpen Source
Open Source denotes software for which the original source code is made freely available
and may be redistributed and modified. Open Source **must be** delivered via a licensing
model, see GPL. and contributions are welcome.

## Feedback

If you have questions or feedback about building a custom sync provider, please 
share them in a comment on this post or in the [#hosting](https://make.wordpress.org/core/tag/hosting/)
channel of [Make WordPress Slack](https://make.wordpress.org/chat/).

_Props to [@jorbin](https://profiles.wordpress.org/jorbin/) and [@westonruter](https://profiles.wordpress.org/westonruter/)
for feedback and contributions._

[#7-0](https://make.wordpress.org/core/tag/7-0/), [#dev-notes](https://make.wordpress.org/core/tag/dev-notes/),
[#dev-notes-7-0](https://make.wordpress.org/core/tag/dev-notes-7-0/), [#feature-real-time-collaboration](https://make.wordpress.org/core/tag/feature-real-time-collaboration/)

 * [Login to Reply](https://login.wordpress.org/?redirect_to=https%3A%2F%2Fmake.wordpress.org%2Fcore%2F2026%2F04%2F01%2Fbuilding-a-custom-sync-provider-for-real-time-collaboration%2F%23respond&locale=en_US)

 [  ](https://profiles.wordpress.org/jorgefilipecosta/) [Jorge Costa](https://profiles.wordpress.org/jorgefilipecosta/)
9:40 pm _on_ March 24, 2026     
Tags: [7-0 ( 53 )](https://make.wordpress.org/core/tag/7-0/),
dev-notes, [dev-notes-7-0 ( 19 )](https://make.wordpress.org/core/tag/dev-notes-7-0/)

# 󠀁[Client-Side Abilities API in WordPress 7.0](https://make.wordpress.org/core/2026/03/24/client-side-abilities-api-in-wordpress-7-0/)󠁿

WordPress 6.9 introduced the [**Abilities API**](https://make.wordpress.org/core/2025/11/10/abilities-api-in-wordpress-6-9/).
The 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. provides a common interface that AI agents, workflow automation tools,
and plugins can use to interact with WordPress. In WordPress 7.0 we continued that
work and now provide a counterpart JavaScriptJavaScript JavaScript or JS is an object-
oriented computer programming language commonly used to create interactive effects
within web browsers. WordPress makes extensive use of JS for a better user experience.
While PHP is executed on the server, JS executes within a user’s browser. [https://www.javascript.com](https://www.javascript.com/)
API that can be used to implement client-side abilities like navigating, or inserting
blocks. This work is fundamental to integrate with browser agents/extensions and
[WebMCP](https://github.com/WordPress/ai/pull/224).

## Two packages

The client-side Abilities API is split into two packages:

 * **`@wordpress/abilities`**: A pure state management package with no WordPress
   server dependencies. It provides the store, registration functions, querying,
   and execution logic. Use this when you only need the abilities store without 
   loading server-registered abilities. This package could also be used in non-WordPress
   projects.
 * **`@wordpress/core-abilities`** :The WordPress integration layer. When loaded,
   it automatically fetches all abilities and categories registered on the server
   via 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/](https://developer.wordpress.org/rest-api/)(`/
   wp-abilities/v1/`) and registers them in the `@wordpress/abilities` store with
   appropriate callbacks.

## Getting started

To use the Abilities API in your pluginPlugin A plugin is a piece of software containing
a group of functions that can be added to a WordPress website. They can extend functionality
or add new features to your WordPress websites. WordPress plugins are written in
the PHP programming language and integrate seamlessly with WordPress. These can 
be free in the WordPress.org Plugin Directory [https://wordpress.org/plugins/](https://wordpress.org/plugins/)
or can be cost-based plugin from a third-party., you need to enqueue the appropriate
script module.

### When your plugin needs server-registered abilities

If your plugin needs access to abilities registered on the server (e.g., coreCore
Core is the set of software required to run WordPress. The Core Development Team
builds WordPress. abilities), enqueue `@wordpress/core-abilities`. This is the most
common case:

    ```notranslate
    add_action( 'admin_enqueue_scripts', 'my_plugin_enqueue_abilities' );
    function my_plugin_enqueue_abilities() {
        wp_enqueue_script_module( '@wordpress/core-abilities' );
    }
    ```

This will load both `@wordpress/core-abilities` and its dependency `@wordpress/abilities`,
and automatically fetch and register all server-side abilities.

### When your plugin only registers client-side abilities

If your plugin only needs to register and work with its own client-side abilities
on a specific page, without needing server-registered abilities, you can enqueue
just `@wordpress/abilities`:

    ```notranslate
    add_action( 'admin_enqueue_scripts', 'my_plugin_enqueue_abilities' );
    function my_plugin_enqueue_abilities( $hook_suffix ) {
        if ( 'my-plugin-page' !== $hook_suffix ) {
            return;
        }
        wp_enqueue_script_module( '@wordpress/abilities' );
    }
    ```

### Importing in JavaScript

Abilities API should be imported as a dynamic import, for example:

    ```notranslate
    const {
        registerAbility,
        registerAbilityCategory,
        getAbilities,
        executeAbility,
    } = await import( '@wordpress/abilities' );
    ```

If your client code is also a script module relying on `@wordpress/scripts`, you
can just use the following code like any other import:

    ```notranslate
    import {
        registerAbility,
        registerAbilityCategory,
        getAbilities,
        executeAbility,
    } from '@wordpress/abilities';
    ```

## Registering abilities

### Register a categoryCategory The 'category' taxonomy lets you group posts / content together that share a common bond. Categories are pre-defined and broad ranging. first

Abilities are organized into categories. Before registering an ability, its category
must exist. Server-side categories are loaded automatically when `@wordpress/core-
abilities` is enqueued. To register a client-side category:

    ```notranslate
    const { registerAbilityCategory } = await import( '@wordpress/abilities' );

    registerAbilityCategory( 'my-plugin-actions', {
        label: 'My Plugin Actions',
        description: 'Actions provided by My Plugin',
    } );
    ```

Category slugs must be lowercase alphanumeric with dashes only (e.g., `data-retrieval`,`
user-management`).

### Register an ability

    ```notranslate
    const { registerAbility } = await import( '@wordpress/abilities' );

    registerAbility( {
        name: 'my-plugin/navigate-to-settings',
        label: 'Navigate to Settings',
        description: 'Navigates to the plugin settings page',
        category: 'my-plugin-actions',
        callback: async () => {
            window.location.href = '/wp-admin/options-general.php?page=my-plugin';
            return { success: true };
        },
    } );
    ```

#### Input and output schemas

Abilities should define 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 (draft-04) for input
validation and output validation:

    ```notranslate
    registerAbility( {
        name: 'my-plugin/create-item',
        label: 'Create Item',
        description: 'Creates a new item with the given title and content',
        category: 'my-plugin-actions',
        input_schema: {
            type: 'object',
            properties: {
                title: { type: 'string', description: 'The title of the item', minLength: 1 },
                content: { type: 'string', description: 'The content of the item' },
                status: { type: 'string', description: 'The publish status of the item', enum: [ 'draft', 'publish' ] },
            },
            required: [ 'title' ],
        },
        output_schema: {
            type: 'object',
            properties: {
                id: { type: 'number', description: 'The unique identifier of the created item' },
                title: { type: 'string', description: 'The title of the created item' },
            },
            required: [ 'id' ],
        },
        callback: async ( { title, content, status = 'draft' } ) => {
            // Create the item...
            return { id: 123, title };
        },
    } );
    ```

When `executeAbility` is called, the input is validated against `input_schema` before
execution and the output is validated against `output_schema` after execution. If
validation fails, an error is thrown with the code `ability_invalid_input` or `ability_invalid_output`.

#### Permission callbacks

Abilities can include a `permissionCallback` that is checked before execution:

    ```notranslate
    registerAbility( {
        name: 'my-plugin/admin-action',
        label: 'Admin Action',
        description: 'An action only available to administrators',
        category: 'my-plugin-actions',
        permissionCallback: () => {
            return currentUserCan( 'manage_options' );
        },
        callback: async () => {
            // Only runs if permissionCallback returns true
            return { success: true };
        },
    } );
    ```

If the permission callback returns `false`, an error with code `ability_permission_denied`
is thrown.

## Querying abilities

### Direct function calls

    ```notranslate
    const {
        getAbilities,
        getAbility,
        getAbilityCategories,
        getAbilityCategory,
    } = await import( '@wordpress/abilities' );

    // Get all registered abilities
    const abilities = getAbilities();

    // Filter abilities by category
    const dataAbilities = getAbilities( { category: 'data-retrieval' } );

    // Get a specific ability by name
    const ability = getAbility( 'my-plugin/create-item' );

    // Get all categories
    const categories = getAbilityCategories();

    // Get a specific category
    const category = getAbilityCategory( 'data-retrieval' );
    ```

### Using with ReactReact React is a JavaScript library that makes it easy to reason about, construct, and maintain stateless and stateful user interfaces. 󠀁[https://reactjs.org](https://reactjs.org/)󠁿 and `@wordpress/data`

The abilities store (`core/abilities`) integrates with `@wordpress/data`, so you
can use `useSelect` for reactive queries in React components:

    ```notranslate
    import { useSelect } from '@wordpress/data';
    import { store as abilitiesStore } from '@wordpress/abilities';

    function AbilitiesList() {
    	// Get all abilities reactively
    	const abilities = useSelect(
    		( select ) => select( abilitiesStore ).getAbilities(),
    		[]
    	);

    	// Filter by category
    	const dataAbilities = useSelect(
    		( select ) =>
    			select( abilitiesStore ).getAbilities( {
    				category: 'data-retrieval',
    			} ),
    		[]
    	);

    	// abilities and dataAbilities update automatically when the store changes
    }
    ```

## Executing abilities

Use `executeAbility` to run any registered ability, whether client-side or server-
side:

    ```notranslate
    import { executeAbility } from '@wordpress/abilities';

    try {
        const result = await executeAbility( 'my-plugin/create-item', {
            title: 'New Item',
            content: 'Item content',
            status: 'draft',
        } );
        console.log( 'Created item:', result.id );
    } catch ( error ) {
        switch ( error.code ) {
            case 'ability_permission_denied':
                console.error( 'You do not have permission to run this ability.' );
                break;
            case 'ability_invalid_input':
                console.error( 'Invalid input:', error.message );
                break;
            case 'ability_invalid_output':
                console.error( 'Unexpected output:', error.message );
                break;
            default:
                console.error( 'Execution failed:', error.message );
        }
    }
    ```

For server-side abilities (those registered via PHPPHP The web scripting language
in which WordPress is primarily architected. WordPress requires PHP 7.4 or higher
and loaded by `@wordpress/core-abilities`), execution is handled automatically via
the REST API. The HTTPHTTP HTTP is an acronym for Hyper Text Transfer Protocol. 
HTTP is the underlying protocol used by the World Wide Web and this protocol defines
how messages are formatted and transmitted, and what actions Web servers and browsers
should take in response to various commands. method used depends on the ability’s
annotations:

 * **`readonly: true`**: uses `GET`
 * **`destructive: true` + `idempotent: true`**: uses `DELETE`
 * **All other cases**: uses `POST`

## Annotations

Abilities support metadata annotations that describe their behavior:

    ```notranslate
    registerAbility( {
        name: 'my-plugin/get-stats',
        label: 'Get Stats',
        description: 'Returns plugin statistics',
        category: 'my-plugin-actions',
        callback: async () => {
            return { views: 100 };
        },
        meta: {
            annotations: {
                readonly: true,
            },
        },
    } );
    ```

Available annotations:

| Annotation | Type | Description | 
| `readonly` | `boolean` | The ability only reads data, does not modify state | 
| `destructive` | `boolean` | The ability performs destructive operations | 
| `idempotent` | `boolean` | The ability can be called multiple times with the same result |

## Unregistering

Client-registered abilities and categories can be removed:

    ```notranslate
    const { unregisterAbility, unregisterAbilityCategory } = await import( '@wordpress/abilities' );

    unregisterAbility( 'my-plugin/navigate-to-settings' );
    unregisterAbilityCategory( 'my-plugin-actions' );
    ```

## Server-side abilities

Abilities registered on the server via the PHP API (`wp_register_ability()`, `wp_register_ability_category()`)
are automatically made available on the client when `@wordpress/core-abilities` 
is loaded. WordPress core enqueues `@wordpress/core-abilities` on all adminadmin(
and super admin) pages, so server abilities are available by default in the admin.

Plugins that register server-side abilities do not need any additional client-side
setup. The abilities will be fetched from the REST API and registered in the client
store automatically.

[#7-0](https://make.wordpress.org/core/tag/7-0/), [#dev-notes](https://make.wordpress.org/core/tag/dev-notes/),
[#dev-notes-7-0](https://make.wordpress.org/core/tag/dev-notes-7-0/)

 * [Login to Reply](https://login.wordpress.org/?redirect_to=https%3A%2F%2Fmake.wordpress.org%2Fcore%2F2026%2F03%2F24%2Fclient-side-abilities-api-in-wordpress-7-0%2F%23respond&locale=en_US)

 [  ](https://profiles.wordpress.org/flixos90/) [Felix Arntz](https://profiles.wordpress.org/flixos90/)
9:18 pm _on_ March 24, 2026     
Tags: [#core-ai ( 3 )](https://make.wordpress.org/core/tag/core-ai/),
[7-0 ( 53 )](https://make.wordpress.org/core/tag/7-0/), dev-notes, [dev-notes-7-0 ( 19 )](https://make.wordpress.org/core/tag/dev-notes-7-0/)

# 󠀁[Introducing the AI Client in WordPress 7.0](https://make.wordpress.org/core/2026/03/24/introducing-the-ai-client-in-wordpress-7-0/)󠁿

WordPress 7.0 includes a built-in AI Client — a provider-agnostic PHPPHP The web
scripting language in which WordPress is primarily architected. WordPress requires
PHP 7.4 or higher 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. that lets plugins send prompts to AI models and receive
results through a consistent interface. Your pluginPlugin A plugin is a piece of
software containing a group of functions that can be added to a WordPress website.
They can extend functionality or add new features to your WordPress websites. WordPress
plugins are written in the PHP programming language and integrate seamlessly with
WordPress. These can be free in the WordPress.org Plugin Directory [https://wordpress.org/plugins/](https://wordpress.org/plugins/)
or can be cost-based plugin from a third-party. describes _what_ it needs and _how_
it needs it. WordPress handles routing the request to a suitable model from a provider
the site owner has configured.

This post explains the API surface, walks through code examples, and covers what
plugin developers need to know.

## The entry point: `wp_ai_client_prompt()`

Every interaction starts with:

    ```php
    $builder = wp_ai_client_prompt();
    ```

This returns a `WP_AI_Client_Prompt_Builder` object, a fluent builder that offers
a myriad of ways to customize your prompt. You chain configuration methods and then
call a generation method to receive a result:

    ```php
    $text = wp_ai_client_prompt( 'Summarize the benefits of caching in WordPress.' )
        ->using_temperature( 0.7 )
        ->generate_text();
    ```

You can pass the prompt text directly as a parameter to `wp_ai_client_prompt()` 
for convenience, though alternatively the `with_text()` method is available for 
building the prompt incrementally.

## Text generation

Here’s a basic text generation example:

    ```php
    $text = wp_ai_client_prompt( 'Write a haiku about WordPress.' )
        ->generate_text();

    if ( is_wp_error( $text ) ) {
        // Handle error.
        return;
    }

    echo wp_kses_post( $text );
    ```

You can pass a 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 so that the model returns 
structured data as a JSON string:

    ```php
    $schema = array(
        'type'  => 'array',
        'items' => array(
            'type'       => 'object',
            'properties' => array(
                'plugin_name' => array( 'type' => 'string' ),
                'category'    => array( 'type' => 'string' ),
            ),
            'required' => array( 'plugin_name', 'category' ),
        ),
    );

    $json = wp_ai_client_prompt( 'List 5 popular WordPress plugins with their primary category.' )
        ->as_json_response( $schema )
        ->generate_text();

    if ( is_wp_error( $json ) ) {
        // Handle error.
        return;
    }

    $data = json_decode( $json, true );
    ```

You can request multiple response candidates as variations for the same prompt:

    ```php
    $texts = wp_ai_client_prompt( 'Write a tagline for a photography blog.' )
        ->generate_texts( 4 );
    ```

## Image generation

Here’s a basic image generation example:

    ```php
    use WordPress\AiClient\Files\DTO\File;

    $image_file = wp_ai_client_prompt( 'A futuristic WordPress logo in neon style' )
        ->generate_image();

    if ( is_wp_error( $image_file ) ) {
        // Handle error.
        return;
    }

    echo '<img src="' . esc_url( $image_file->getDataUri() ) . '" alt="">';
    ```

`generate_image()` returns a `File` DTO with access to the image data via `getDataUri()`.

Similar to text generation, you can request multiple variations of the same image:

    ```php
    $images = wp_ai_client_prompt( 'Aerial shot of snowy plains, cinematic.' )
        ->generate_images( 4 );

    if ( is_wp_error( $images ) ) {
        // Handle error.
        return;
    }

    foreach ( $images as $image_file ) {
        echo '<img src="' . esc_url( $image_file->getDataUri() ) . '">';
    }
    ```

## Getting the full result object

For richer metadata, e.g. covering provider and model information, use `generate_*
_result()` instead. For example, for image generation:

    ```php
    $result = wp_ai_client_prompt( 'A serene mountain landscape.' )
        ->generate_image_result();
    ```

This returns a `GenerativeAiResult` object that provides several pieces of additional
information, including token usage and which provider and which model responded 
to the prompt. The most relevant methods for this additional metadata are:

 * `getTokenUsage()`: Returns the token usage, broken down by input, output, and
   optionally thinking.
 * `getProviderMetadata()`: Returns metadata about the provider that handled the
   request.
 * `getModelMetadata()`: Returns metadata about the model that handled the request(
   through the provider).

The `GenerativeAiResult` object is serializable and can be passed directly to `rest_ensure_response()`,
making it straightforward to expose AI features through 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/](https://developer.wordpress.org/rest-api/).

Available `generate_*_result()` methods:

 * `generate_text_result()`
 * `generate_image_result()`
 * `convert_text_to_speech_result()`
 * `generate_speech_result()`
 * `generate_video_result()`

Use the appropriate method for the modality you are working with. Each returns a`
GenerativeAiResult` object with rich metadata.

### Model preferences

The models available on each WordPress site depends on which AI providers the administrators
of that site have configured in the **Settings > Connectors** screen.

Since your plugin doesn’t control which providers are available on each site, use`
using_model_preference()` to indicate which models would be ideal. The AI Client
will use the first model from that list that is available, falling back to any compatible
model if none are available:

    ```php
    $text_result = wp_ai_client_prompt( 'Summarize the history of the printing press.' )
        ->using_temperature( 0.1 )
        ->using_model_preference(
            'claude-sonnet-4-6',
            'gemini-3.1-pro-preview',
            'gpt-5.4'
        )
        ->generate_text_result();
    ```

This is a preference, not a requirement. Your plugin should function without it.
Keep in mind that you can test or verify which model was used by looking at the 
full result object, under the `providerMetadata` and `modelMetadata` properties.

If you don’t specify a model preference, the first model encountered across the 
configured providers that is suitable will be used. It is up to the individual provider
implementations to sort the provider’s models in a reasonable manner, e.g. so that
more recent models appear before older models of the same model family. The three
initial [official provider plugins](https://make.wordpress.org/core/tag/dev-notes/?output_format=md#official-provider-plugins)(
see below) organize models in that way, as recommended.

## Feature detection

Not every WordPress site will have an AI provider configured, and not every provider
supports every capabilitycapability A **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).
and every option. Before showing AI-powered UIUI User interface, check whether the
feature can work:

    ```php
    $builder = wp_ai_client_prompt( 'test' )
        ->using_temperature( 0.7 );

    if ( $builder->is_supported_for_text_generation() ) {
        // Safe to show text generation UI.
    }
    ```

These checks do not make API calls. They use deterministic logic to match the builder’s
configuration against the capabilitiescapability A **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). of available models. As such, they are fast to run and there is no 
cost incurred by calling them.

Available support check methods:

 * `is_supported_for_text_generation()`
 * `is_supported_for_image_generation()`
 * `is_supported_for_text_to_speech_conversion()`
 * `is_supported_for_speech_generation()`
 * `is_supported_for_video_generation()`

Use these to conditionally load your UI, show a helpful notice when the feature 
is unavailable, or skip registering UI altogether. Never assume that AI features
will be available just because WordPress 7.0 is installed.

## Advanced configuration

### System instructions

    ```php
    $text = wp_ai_client_prompt( 'Explain caching.' )
        ->using_system_instruction( 'You are a WordPress developer writing documentation.' )
        ->generate_text();
    ```

### Max tokens

    ```php
    $text = wp_ai_client_prompt( 'Explain quantum computing in complicated terms.' )
        ->using_max_tokens( 8000 )
        ->generate_text();
    ```

### Output file type and orientation for images

    ```php
    use WordPress\AiClient\Files\Enums\FileTypeEnum;
    use WordPress\AiClient\Files\Enums\MediaOrientationEnum;

    $result = wp_ai_client_prompt()
        ->with_text( 'A vibrant sunset over the ocean.' )
        ->as_output_file_type( FileTypeEnum::inline() )
        ->as_output_media_orientation( MediaOrientationEnum::from( 'landscape' ) )
        ->generate_image_result();
    ```

### Multimodal output

    ```php
    use WordPress\AiClient\Messages\Enums\ModalityEnum;

    $result = wp_ai_client_prompt( 'Create a recipe for a chocolate cake and include photos for the steps.' )
        ->as_output_modalities( ModalityEnum::text(), ModalityEnum::image() )
        ->generate_result();

    if ( is_wp_error( $result ) ) {
        // Handle error.
        return;
    }

    foreach ( $result->toMessage()->getParts() as $part ) {
        if ( $part->isText() ) {
            echo wp_kses_post( $part->getText() );
        } elseif ( $part->isFile() && $part->getFile()->isImage() ) {
            echo '<img src="' . esc_url( $part->getFile()->getDataUri() ) . '">';
        }
    }
    ```

### Additional builder methods

The full list of configuration methods is available via the `WP_AI_Client_Prompt_Builder`
class. Key methods include:

| **Configuration** | **Method** | 
| Prompt text | `with_text()` | 
| File input | `with_file()` | 
| Conversation history (relevant for multi-turn / chats) | `with_history()` | 
| System instruction | `using_system_instruction()` | 
| Temperature | `using_temperature()` | 
| Max tokens | `using_max_tokens()` | 
| Top-p / Top-k | `using_top_p(), using_top_k()` | 
| Stop sequences | `using_stop_sequences()` | 
| Model preference | `using_model_preference()` | 
| Output modalities | `as_output_modalities()` | 
| Output file type | `as_output_file_type()` | 
| JSON response | `as_json_response()` |

## Error handling

`wp_ai_client_prompt()` generator methods return `WP_Error` on failure, following
WordPress conventions:

    ```php
    $text = wp_ai_client_prompt( 'Hello' )
        ->generate_text();

    if ( is_wp_error( $text ) ) {
        // Handle the error.
    }
    ```

When used in a REST API callback, both `GenerativeAiResult` and `WP_Error` can be
passed to `rest_ensure_response()` directly:

    ```php
    function my_rest_callback( WP_REST_Request $request ) {
        $result = wp_ai_client_prompt( $request->get_param( 'prompt' ) )
            ->generate_text_result();

        return rest_ensure_response( $result );
    }
    ```

If an error occurs, it will automatically have a semantically meaningful HTTPHTTP
HTTP is an acronym for Hyper Text Transfer Protocol. HTTP is the underlying protocol
used by the World Wide Web and this protocol defines how messages are formatted 
and transmitted, and what actions Web servers and browsers should take in response
to various commands. response code attached to it.

## Controlling AI availability

For granular control, the `wp_ai_client_prevent_prompt` filterFilter Filters are
one of the two types of Hooks [https://codex.wordpress.org/Plugin_API/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. allows preventing specific prompts from executing:

    ```php
    add_filter(
        'wp_ai_client_prevent_prompt',
        function ( bool $prevent, WP_AI_Client_Prompt_Builder $builder ): bool {
            // Example: Block all prompts for non-admin users.
            if ( ! current_user_can( 'manage_options' ) ) {
                return true;
            }
            return $prevent;
        },
        10,
        2
    );
    ```

When a prompt is prevented:

 * No AI call is attempted.
 * `is_supported_*()` methods return `false`, allowing plugins to gracefully hide
   their UI.
 * `generate_*()` methods return a `WP_Error`.

## Architecture

The AI Client in WordPress 7.0 consists of two layers:

 1. **PHP AI Client** ([wordpress/php-ai-client](https://github.com/WordPress/php-ai-client))—
    A provider-agnostic PHP SDK bundled in CoreCore Core is the set of software required
    to run WordPress. The Core Development Team builds WordPress. as an external library.
    This is the engine that handles provider communication, model selection, and response
    normalization. Since it is technically a WordPress agnostic PHP SDK which other
    PHP projects can use too, it uses camelCase method naming and makes use of exceptions.

 2. **WordPress wrapper** — Core’s `WP_AI_Client_Prompt_Builder` class wraps the PHP
    AI Client with WordPress conventions: snake_case methods, `WP_Error` returns, and
    integration with WordPress HTTP transport, the Abilities API, the Connectors/Settings
    infrastructure, and the WordPress 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. system.

The `wp_ai_client_prompt()` function is the recommended entry point. It returns 
a `WP_AI_Client_Prompt_Builder` instance that catches exceptions from the underlying
SDK and converts them to `WP_Error` objects.

### Credential management

API keys are managed through the [Connectors API](https://make.wordpress.org/core/2026/03/18/introducing-the-connectors-api-in-wordpress-7-0/).
AI provider plugins that register with the PHP AI Client’s provider registry get
automatic connector integration — including the **Settings > Connectors** adminadmin(
and super admin) UI for API key management. Plugin developers using the AI Client
to build features do not need to handle credentials at all.

### Official provider plugins

WordPress Core does not bundle any AI providers directly. Instead, they are developed
and maintained as plugins, which allows for more flexible and rapid iteration speed,
in accordance with how fast AI evolves. The AI Client in WordPress Core provides
the stable foundation, and as an abstraction layer is sufficiently detached from
provider specific requirements that may change overnight.

While anyone is able to implement new provider plugins, the WordPress project itself
has developed three initial flagship implementations, to integrate with the most
popular AI providers. These plugins are:

 * [AI Provider for Anthropic](https://wordpress.org/plugins/ai-provider-for-anthropic/)
 * [AI Provider for Google](https://wordpress.org/plugins/ai-provider-for-google/)
 * [AI Provider for OpenAI](https://wordpress.org/plugins/ai-provider-for-openai/)

### Separately available: Client-side JavaScriptJavaScript JavaScript or JS is an object-oriented computer programming language commonly used to create interactive effects within web browsers. WordPress makes extensive use of JS for a better user experience. While PHP is executed on the server, JS executes within a user’s browser. 󠀁[https://www.javascript.com](https://www.javascript.com/)󠁿 API

A JavaScript API with a similar fluent prompt builder is available via the [wp-ai-client](https://github.com/WordPress/wp-ai-client)
package. It uses REST endpoints under the hood to connect to the server-side infrastructure.
This API is not part of Core, and it is still being evaluated whether this approach
is scalable for general use. Because the API allows arbitrary prompt execution from
the client-side, it requires a high-privilege capability check, which by default
is only granted to administrators. This restriction is necessary to prevent untrusted
users from sending any prompt to any configured AI provider. As such, using this
approach in a distributed plugin is not recommended.

For now, the recommended approach is to implement individual REST API endpoints 
for each specific AI feature your plugin provides, and have your JavaScript functionality
call those endpoints. This allows you to enforce granular permission checks and 
limit the scope of what can be executed from the client-side. It also keeps the 
actual AI prompt handling and configuration fully scoped to be server-side only.

## MigrationMigration Moving the code, database and media files for a website site from one server to another. Most typically done when changing hosting companies. from php-ai-client and wp-ai-client

If you have been using these packages in your plugin(s) before, here’s what to know.

### Recommended: require WordPress 7.0

The simplest path is to update your plugin’s Requires at least headerHeader The 
header of your site is typically the first thing people will experience. The masthead
or header art located across the top of your page is part of the look and feel of
your website. It can influence a visitor’s opinion about your content and you/ your
organization’s brand. It may also look different on different screen sizes. to 7.0
and remove the Composer dependencies on `wordpress/php-ai-client` and its transitive
dependencies.

Replace any `AI_Client::prompt()` calls with `wp_ai_client_prompt()`.

For the `wordpress/wp-ai-client` package, if you are not using the package’s REST
API endpoints or JavaScript API, you can simply remove it as a dependency, since
everything else it does is now part of WordPress Core.

### If you must support WordPress < 7.0

#### PHP AI Client (`wordpress/php-ai-client`)

If your plugin still needs to run on WordPress versions before 7.0 while also bundling
wordpress/php-ai-client, you will need a conditional autoloader workaround. The 
PHP AI Client and its dependencies are now loaded by Core on 7.0+, so loading them
again via Composer will cause conflicts (duplicate class definitions).

The solution: only register your Composer autoloader for these dependencies when
running on WordPress versions before 7.0:

    ```php
    if ( ! function_exists( 'wp_get_wp_version' ) || version_compare( wp_get_wp_version(), '7.0', '<' ) ) {
        require_once __DIR__ . '/vendor/autoload.php';
    }
    ```

Due to how Composer’s autoloader works — loading all dependencies at once rather
than selectively — a more granular approach was not feasible. This means the conditional
check needs to wrap the entire autoloader. Alternatively, break your PHP dependencies
apart in two separate Composer setups, one that can always be autoloaded, and another
one for the `wordpress/php-ai-client` package and its dependencies only, which would
be conditionally autoloaded.

#### WP AI Client (`wordpress/wp-ai-client`)

The `wordpress/wp-ai-client` package handles the WordPress 7.0 transition automatically.
On 7.0+, it disables its own PHP SDK infrastructure (since Core handles it natively)
but keeps the REST API endpoints and JavaScript API active, as those aren’t in Core
yet.

You can continue loading this package unconditionally. It detects the WordPress 
version and only activates the parts that aren’t already provided by Core. No conditional
loading needed. However, make sure to stay up to date on this package, because it
will likely be discontinued soon, in favor of moving the REST API endpoints and 
JavaScript API into 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/](https://wordpress.org/gutenberg/).
There are ongoing discussions on whether these should be merged into Core too, see
[#64872](https://core.trac.wordpress.org/ticket/64872) and [#64873](https://core.trac.wordpress.org/ticket/64873).

See the [WP AI Client upgrade guide](https://github.com/WordPress/wp-ai-client/blob/trunk/UPGRADE.md)
for additional migration details.

## Additional resources

 * 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.: [#64591](https://core.trac.wordpress.org/ticket/64591)
 * [PHP AI Client](https://github.com/WordPress/php-ai-client) (bundled library)
 * [WP AI Client](https://github.com/WordPress/wp-ai-client) (original package, 
   now mostly merged into Core)
 * [Original merge proposal](https://make.wordpress.org/core/2026/02/03/proposal-for-merging-wp-ai-client-into-wordpress-7-0/)

_Props to [@gziolo](https://profiles.wordpress.org/gziolo/) [@nilambar](https://profiles.wordpress.org/nilambar/)
[@laurisaarni](https://profiles.wordpress.org/laurisaarni/) [@justlevine](https://profiles.wordpress.org/justlevine/)
for reviewing this post._

[#core-ai](https://make.wordpress.org/core/tag/core-ai/), [#7-0](https://make.wordpress.org/core/tag/7-0/),
[#dev-notes](https://make.wordpress.org/core/tag/dev-notes/), [#dev-notes-7-0](https://make.wordpress.org/core/tag/dev-notes-7-0/)

 * [Login to Reply](https://login.wordpress.org/?redirect_to=https%3A%2F%2Fmake.wordpress.org%2Fcore%2F2026%2F03%2F24%2Fintroducing-the-ai-client-in-wordpress-7-0%2F%23respond&locale=en_US)

 [  ](https://profiles.wordpress.org/gziolo/) [Greg Ziółkowski](https://profiles.wordpress.org/gziolo/)
9:06 am _on_ March 18, 2026     
Tags: [7-0 ( 53 )](https://make.wordpress.org/core/tag/7-0/),
dev-notes, [dev-notes-7-0 ( 19 )](https://make.wordpress.org/core/tag/dev-notes-7-0/)

# 󠀁[Introducing the Connectors API in WordPress 7.0](https://make.wordpress.org/core/2026/03/18/introducing-the-connectors-api-in-wordpress-7-0/)󠁿

WordPress 7.0 introduces the **Connectors 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 framework for registering
and managing connections to external services. The initial focus is on AI providers,
giving WordPress a standardized way to handle API key management, provider discovery,
and adminadmin (and super admin) UIUI User interface for configuring AI services.

This post walks through what the Connectors API does, how it works under the hood,
and what 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/](https://wordpress.org/plugins/)
or can be cost-based plugin from a third-party. developers need to know.

## Table of Contents

 * [What is a connector?](https://make.wordpress.org/core/tag/dev-notes/?output_format=md#what-is-a-connector)
 * [How AI providers are auto-discovered](https://make.wordpress.org/core/tag/dev-notes/?output_format=md#how-ai-providers-are-auto-discovered)
 * [The Settings > Connectors admin screen](https://make.wordpress.org/core/tag/dev-notes/?output_format=md#the-settings--connectors-admin-screen)
 * [Authentication and API key management](https://make.wordpress.org/core/tag/dev-notes/?output_format=md#authentication-and-api-key-management)
 * [Public API functions](https://make.wordpress.org/core/tag/dev-notes/?output_format=md#public-api-functions)
 * [Overriding connector metadata](https://make.wordpress.org/core/tag/dev-notes/?output_format=md#overriding-connector-metadata)
 * [The initialization lifecycle](https://make.wordpress.org/core/tag/dev-notes/?output_format=md#the-initialization-lifecycle)
 * [Looking ahead](https://make.wordpress.org/core/tag/dev-notes/?output_format=md#looking-ahead)

## What is a connector?

A connector represents a connection to an external service. Each connector carries
standardized metadata — a display name, description, logo, authentication configuration,
and an optional association with a WordPress.orgWordPress.org The community site
where WordPress code is created and shared by the users. This is where you can download
the source code for WordPress core, plugins and themes as well as the central location
for community conversations and organization. [https://wordpress.org/](https://wordpress.org/)
plugin. The system currently focuses on providers that authenticate with an API 
key, but the architecture is designed to support additional connector types in future
releases.

WordPress 7.0 comes with three featured connectors—Anthropic, Google, and OpenAI—
accessible from the new **Settings → Connectors** screen, making installation seamless.

Each connector is stored as an associative array with the following shape:

    ```notranslate
    array(
        'name'           => 'Anthropic',
        'description'    => 'Text generation with Claude.',
        'logo_url'       => 'https://example.com/anthropic-logo.svg',
        'type'           => 'ai_provider',
        'authentication' => array(
            'method'          => 'api_key',
            'credentials_url' => 'https://platform.claude.com/settings/keys',
            'setting_name'    => 'connectors_ai_anthropic_api_key',
        ),
        'plugin'         => array(
            'file' => 'ai-provider-for-anthropic/plugin.php',
        ),
    )
    ```

## How AI providers are auto-discovered

If you’re building an AI provider plugin that integrates with the WP AI Client, 
you don’t need to register a connector manually. The Connectors API automatically
discovers providers from the WP AI Client’s default registry and creates connectors
with the correct metadata.

Here’s what happens during initialization:

 1. Built-in connectors (Anthropic, Google, OpenAI) are registered with hardcoded defaults.
 2. The system queries the `AiClient::defaultRegistry()` for all registered providers.
 3. For each provider, metadata (name, description, logo, authentication method) is
    merged on top of the defaults, with provider registry values taking precedence.
 4. The `wp_connectors_init` action fires so plugins can override metadata or register
    additional connectors.

**In short:** if your AI provider plugin registers with the WP AI Client, the connector
is created for you. No additional code is needed.

## The Settings > Connectors admin screen

Registered connectors appear on a new **Settings > Connectors** admin screen. The
screen renders each connector as a card, and the registry data drives what’s displayed:

 * **`name`**, **`description`**, and **`logo_url`** are shown on the card.
 * **`plugin.file`** — the value is the plugin’s main file path relative to the 
   plugins directory (e.g., `akismet/akismet.php` or `hello.php`). The screen uses
   it to check whether the associated plugin is installed and active, and shows 
   the appropriate action button.
 * **`authentication.credentials_url`** is rendered as a link directing users to
   the provider’s site to obtain API credentials.
 * For **`api_key`** connectors, the screen shows the current key source (environment
   variable, PHPPHP The web scripting language in which WordPress is primarily architected.
   WordPress requires PHP 7.4 or higher constant, or database) and connection status.

Connectors with other authentication methods are stored in the PHP registry and 
exposed via the script module data, but currently require a client-side JavaScriptJavaScript
JavaScript or JS is an object-oriented computer programming language commonly used
to create interactive effects within web browsers. WordPress makes extensive use
of JS for a better user experience. While PHP is executed on the server, JS executes
within a user’s browser. [https://www.javascript.com](https://www.javascript.com/)
registration for custom frontend UI.

## Authentication and API key management

Connectors support two authentication methods:

 * **`api_key`** — Requires an API key, which can be provided via environment variable,
   PHP constant, or the database (checked in that order).
 * **`none`** — No authentication required.

The authentication method (`api_key` or `none`) is determined by the authentication
metadata registered with the connector. For providers using `api_key`, a database
setting name is automatically generated using the pattern `connectors_{$provider_type}_{
$provider_id}_api_key`. It’s also possible to set a custom name using `setting_name`
property. API keys stored in the database are not encrypted but are masked in the
user interface. Encryption is being explored in a follow-up ticketticket Created
for both bug reports and feature development on the bug tracker.: [#64789](https://core.trac.wordpress.org/ticket/64789).

For AI providers, there is a specific naming convention in place for environment
variables and PHP constants: `{PROVIDER_ID}_API_KEY` (e.g., the `anthropic` provider
maps to `ANTHROPIC_API_KEY`). For other types of providers, an environment variable(`
env_var_name`) and a PHP constant (`constant_name`) can be optionally set to any
value.

### API key source priority

For `api_key` connectors, the system looks for a setting value in this order:

 1. **Environment variable** — e.g., `ANTHROPIC_API_KEY`
 2. **PHP constant** — e.g., `define( 'ANTHROPIC_API_KEY', 'sk-...' );`
 3. **Database** — stored through the admin screen, e.g. `connectors_ai_anthropic_api_key`
    setting

## Public API functions

The Connectors API provides three public functions for querying the registry. These
are available after `init`.

### `wp_is_connector_registered()`

Checks if a connector is registered:

    ```notranslate
    if ( wp_is_connector_registered( 'anthropic' ) ) {
        // The Anthropic connector is available.
    }
    ```

### `wp_get_connector()`

Retrieves a single connector’s data:

    ```notranslate
    $connector = wp_get_connector( 'anthropic' );
    if ( $connector ) {
        echo $connector['name']; // 'Anthropic'
    }
    ```

Returns an associative array with keys: `name`, `description`, `type`, `authentication`,
and optionally `logo_url` and `plugin`. Returns `null` if the connector is not registered.

### `wp_get_connectors()`

Retrieves all registered connectors, keyed by connector ID:

    ```notranslate
    $connectors = wp_get_connectors();
    foreach ( $connectors as $id => $connector ) {
        printf( '%s: %s', $connector['name'], $connector['description'] );
    }
    ```

## Overriding connector metadata

The `wp_connectors_init` action fires after all built-in and auto-discovered connectors
have been registered. Plugins can use this hook to override metadata on existing
connectors.

Since the registry rejects duplicate IDs, overriding requires an unregister, modify,
register sequence:

    ```notranslate
    add_action( 'wp_connectors_init', function ( WP_Connector_Registry $registry ) {
        if ( $registry->is_registered( 'anthropic' ) ) {
            $connector = $registry->unregister( 'anthropic' );
            $connector['description'] = __( 'Custom description for Anthropic.', 'my-plugin' );
            $registry->register( 'anthropic', $connector );
        }
    } );
    ```

Key points about the override pattern:

 * Always check `is_registered()` before calling `unregister()` — calling `unregister()`
   on a non-existent connector triggers a `_doing_it_wrong()` notice.
 * `unregister()` returns the connector data, which you can modify and pass back
   to `register()`.
 * Connector IDs must match the pattern `/^[a-z0-9_-]+$/` (lowercase alphanumeric,
   underscores, and hyphens only).

### Registry methods

Within the `wp_connectors_init` callback, the `WP_Connector_Registry` instance provides
these methods:

| Method | Description | 
| `register( $id, $args )` | Register a new connector. Returns the connector data or `null` on failure. | 
| `unregister( $id )` | Remove a connector and return its data. Returns `null` if not found. | 
| `is_registered( $id )` | Check if a connector exists. | 
| `get_registered( $id )` | Retrieve a single connector’s data. | 
| `get_all_registered()` | Retrieve all registered connectors. |

Outside of the `wp_connectors_init` callback, use the public API functions (`wp_get_connector()`,`
wp_get_connectors()`, `wp_is_connector_registered()`) instead of accessing the registry
directly.

## The initialization lifecycle

Understanding the initialization sequence helps when deciding where to hook in:

During the `init` action, `_wp_connectors_init()` runs and:

 * Creates the `WP_Connector_Registry` singleton.
 * Registers built-in connectors (Anthropic, Google, OpenAI) with hardcoded defaults.
 * Auto-discovers providers from the WP AI Client registry and merges their metadata
   on top of defaults.
 * Fires the **`wp_connectors_init`** action — this is where plugins override metadata
   or register additional connectors.

The `wp_connectors_init` action is the only supported entry point for modifying 
the registry. Attempting to set the registry instance outside of `init` triggers
a `_doing_it_wrong()` notice.

## Looking ahead

The Connectors API in WordPress 7.0 was optimized for AI providers, but the underlying
architecture is designed to grow. Currently, only connectors with `api_key` authentication
receive the full admin UI treatment. The PHP registry already accepts any connector
type — what’s missing is the frontend integration for connectors with different 
authentication mechanisms.

Future releases are expected to:

 * Expand support for additional authentication methods beyond `api_key` and `none`.
 * Offer more built-in UI integrations beyond `api_key`.
 * Provide a client-side JavaScript registration API for custom connector UI.

When those capabilitiescapability A **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).
land, the `wp_connectors_init` action will be the primary hook for registering new
connector types.

---

Props to [@jorgefilipecosta](https://profiles.wordpress.org/jorgefilipecosta/), 
[@shaunandrews](https://profiles.wordpress.org/shaunandrews/), [@flixos90](https://profiles.wordpress.org/flixos90/),
[@westonruter](https://profiles.wordpress.org/westonruter/), [@justlevine](https://profiles.wordpress.org/justlevine/),
and others for contributing to the Connectors screen and this dev notedev note Each
important change in WordPress Core is documented in a developers note, (usually 
called dev note). Good dev notes generally include a description of the change, 
the decision that led to this change, and a description of how developers are supposed
to work with that change. Dev notes are published on Make/Core blog during the beta
phase of WordPress release cycle. Publishing dev notes is particularly important
when plugin/theme authors and WordPress developers need to be aware of those changes.
In general, all dev notes are compiled into a Field Guide at the beginning of the
release candidate phase..

[#7-0](https://make.wordpress.org/core/tag/7-0/), [#dev-notes](https://make.wordpress.org/core/tag/dev-notes/),
[#dev-notes-7-0](https://make.wordpress.org/core/tag/dev-notes-7-0/)

 * [Login to Reply](https://login.wordpress.org/?redirect_to=https%3A%2F%2Fmake.wordpress.org%2Fcore%2F2026%2F03%2F18%2Fintroducing-the-connectors-api-in-wordpress-7-0%2F%23respond&locale=en_US)

 [  ](https://profiles.wordpress.org/bernhard-reiter/) [Bernie Reiter](https://profiles.wordpress.org/bernhard-reiter/)
8:30 pm _on_ March 16, 2026     
Tags: [7-0 ( 53 )](https://make.wordpress.org/core/tag/7-0/),
dev-notes, [dev-notes-7-0 ( 19 )](https://make.wordpress.org/core/tag/dev-notes-7-0/)

# 󠀁[Pattern Overrides in WP 7.0: Support for Custom Blocks](https://make.wordpress.org/core/2026/03/16/pattern-overrides-in-wp-7-0-support-for-custom-blocks/)󠁿

As of WordPress 7.0, any 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. attribute that supports Block Bindings [also supports Pattern Overrides](https://github.com/WordPress/gutenberg/pull/73889).
So now, you can use Pattern Overrides for any block you want — even custom blocks—
the previous limit to a hardcoded set of CoreCore Core is the set of software required
to run WordPress. The Core Development Team builds WordPress. blocks no longer holds
you back. To get started, opt in through the server-side [`block_bindings_supported_attributes` filter(s)](https://developer.wordpress.org/block-editor/reference-guides/block-api/block-bindings/#extending-supported-attributes).

The underlying Block Bindings mechanism will make sure that:

 * In dynamic blocks, the correct, bound attribute values will be passed to `render_callback()`.
 * In static blocks, 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. 
   is used to locate [attributes sourced from `html`, `rich-text`, or `attribute` sources](https://developer.wordpress.org/block-editor/reference-guides/block-api/block-attributes/#value-source)
   via their selectors in the persisted markup, replacing their values with the 
   respective bound attribute values.

Bound attribute values should appear correctly in the rendered blocks’ markup in
these cases. You shouldn’t need any other modifications.

For static blocks with unsourced attributes, or with sourced attributes whose selectors
are more complex than the HTML API currently understands, you might need to add 
a `render_callback()` or a `render_block` filterFilter Filters are one of the two
types of Hooks [https://codex.wordpress.org/Plugin_API/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 make sure bound attribute values are correctly handled. It’s best if 
you first try without (i.e. by only adding the attribute via `block_bindings_supported_attributes`
filter). Then, if the bound attribute value doesn’t render, add the callback or 
the filter that guarantees the render.

---

_Props to [@fabiankaegy](https://profiles.wordpress.org/fabiankaegy/) and [@marybaum](https://profiles.wordpress.org/marybaum/)
for reviewing this dev notedev note Each important change in WordPress Core is documented
in a developers note, (usually called dev note). Good dev notes generally include
a description of the change, the decision that led to this change, and a description
of how developers are supposed to work with that change. Dev notes are published
on Make/Core blog during the beta phase of WordPress release cycle. Publishing dev
notes is particularly important when plugin/theme authors and WordPress developers
need to be aware of those changes.In general, all dev notes are compiled into a 
Field Guide at the beginning of the release candidate phase.!_

[#7-0](https://make.wordpress.org/core/tag/7-0/), [#dev-notes](https://make.wordpress.org/core/tag/dev-notes/),
[#dev-notes-7-0](https://make.wordpress.org/core/tag/dev-notes-7-0/)

 * [Login to Reply](https://login.wordpress.org/?redirect_to=https%3A%2F%2Fmake.wordpress.org%2Fcore%2F2026%2F03%2F16%2Fpattern-overrides-in-wp-7-0-support-for-custom-blocks%2F%23respond&locale=en_US)

 [  ](https://profiles.wordpress.org/ramonopoly/) [ramonopoly](https://profiles.wordpress.org/ramonopoly/)
10:35 pm _on_ March 15, 2026     
Tags: [7-0 ( 53 )](https://make.wordpress.org/core/tag/7-0/),
dev-notes, [dev-notes-7-0 ( 19 )](https://make.wordpress.org/core/tag/dev-notes-7-0/)

# 󠀁[Pattern Editing in WordPress 7.0](https://make.wordpress.org/core/2026/03/15/pattern-editing-in-wordpress-7-0/)󠁿

WordPress 7.0 expands `contentOnly` editing to unsynced patterns and template parts.

The key behavioral change is that unsynced patterns and template parts inserted 
into the editor now default to `contentOnly` mode, prioritizing the editing of text
and media without exposing the deeper 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 or style controls.

### Pattern-level editing modes

At times a user will want to make design changes to a pattern, and this works differently
depending on the type of pattern.

 * _Unsynced_ — A user can click an ‘Edit pattern’ button or double click the body
   of a pattern, and a spotlight mode engages. In this mode users have full editing
   capabilitiescapability A **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)..
 * _Synced (synced patterns / template parts)_ — Users can click the ‘Edit original’
   button and are taken into an isolated editor when they can make any changes to
   the underlying pattern. The editor headerHeader The header of your site is typically
   the first thing people will experience. The masthead or header art located across
   the top of your page is part of the look and feel of your website. It can influence
   a visitor’s opinion about your content and you/ your organization’s brand. It
   may also look different on different screen sizes. provides navigation back to
   the originating document. Changes to synced patterns apply globally.

## What developers need to do

### Block authors

If your block is nested in a `contentOnly` pattern and should be editable, ensure
attributes that represent a block’s content have `"role": "content"` set in `block.
json`. This is unchanged from WordPress 6.7, but is now more important as `contentOnly`
mode is applied more broadly by default.

    ```language-json
    {
      "attributes": {
        "url": {
          "type": "string",
          "role": "content"
        },
        "label": {
          "type": "string",
          "role": "content"
        }
      }
    }
    ```

Blocks without any `"role": "content"` attributes will be hidden from List View 
and non-selectable inside a `contentOnly` container.

At times a block may not have an appropriate attribute to which to apply `"role":"
content"`. A `"contentRole": true` property can be added to the block supports declaration,
and this has the same effect as `"role": "content"`.

    ```language-json
    {
      "supports": {
        "contentRole": true
      }
    }
    ```

Developers should prefer `"role": "content"` where possible.

### Parent / child contentOnly blocks

Many blocks are considered ‘content’, but consist of both parent and child block
types. Some examples of CoreCore Core is the set of software required to run WordPress.
The Core Development Team builds WordPress. blocks are:

 * List and List Item
 * Gallery and Image
 * Buttons and Button

Whenever both a parent and child block have a `"role": "content"` attribute or `"
contentRole": true` block supports, `contentOnly` mode allows insertion of child
blocks. This behavior has been present since WordPress 6.9, but is now more prominent.

Block developers can take advantage of this behavior.

### List View block support

New for WordPress 7.0, block developers can add a `"listView": true` block supports
declaration. This adds a List View tab to the block inspector with a dedicated List
View UIUI User interface for the block that allows users to easily rearrange and
add inner blocks. This List View is also displayed in Patterns and is recommended
for any block that acts as a container for a list of child blocks.

    ```language-json
    {
      "supports": {
        "listView": true
      }
    }
    ```

### Theme / pattern authors

Patterns that previously relied on unrestricted editing of their inner blocks will
now be presented to users in `contentOnly` mode by default. Review your registered
patterns and consider:

 1. Testing that the content users are expected to change is accessible in `contentOnly`
    mode.
 2. Auditing patterns containing Buttons, List, Social Icons, and Navigation blocks
    specifically — these have had targeted `contentOnly` improvements and may behave
    differently than before.
 3. Restrict the allowed blocks if users shouldn’t be able to insert blocks in a specific
    area of a pattern. If assembling a pattern in a block editor, this can be done 
    using the ‘Manage allowed blocks’ feature in the Advanced section of the block 
    inspector for any blocks that have `"allowedBlocks": true` block support. Through
    code, the `"allowedBlocks":[]` attribute can be added to prevent insertion of inner
    blocks.

### Site admins

A new block editor setting, `disableContentOnlyForUnsyncedPatterns`, allows opting
out of `contentOnly` mode for unsynced patterns. Via PHPPHP The web scripting language
in which WordPress is primarily architected. WordPress requires PHP 7.4 or higher,
use the `block_editor_settings_all` filterFilter Filters are one of the two types
of Hooks [https://codex.wordpress.org/Plugin_API/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.:

    ```php
    add_filter( 'block_editor_settings_all', function( $settings ) {
        $settings['disableContentOnlyForUnsyncedPatterns'] = true;
        return $settings;
    } );
    ```

Or via JavaScriptJavaScript JavaScript or JS is an object-oriented computer programming
language commonly used to create interactive effects within web browsers. WordPress
makes extensive use of JS for a better user experience. While PHP is executed on
the server, JS executes within a user’s browser. [https://www.javascript.com](https://www.javascript.com/):

    ```javascript
    wp.data.dispatch( 'core/block-editor' ).updateSettings( {
        disableContentOnlyForUnsyncedPatterns: true,
    } );
    ```

When `disableContentOnlyForUnsyncedPatterns` is `true`, blocks with `patternName`
metadata are no longer treated as section blocks and their children are not placed
into `contentOnly` editing mode. Template parts and synced patterns (`core/block`)
are unaffected — they remain section blocks regardless of this setting.

### 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/](https://wordpress.org/plugins/)󠁿 or can be cost-based plugin from a third-party. developers

If your plugin interacts with pattern editing state — toolbar controls, 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. panels, List View visibility,
or entity navigation — test against the new editing modes. The `contentOnly` state
is now applied more broadly, and UI components that assume full block access inside
patterns may not render as expected.

Props to [@talldanwp](https://profiles.wordpress.org/talldanwp/) and [@andrewserong](https://profiles.wordpress.org/andrewserong/)
for helping to write this dev notedev note Each important change in WordPress Core
is documented in a developers note, (usually called dev note). Good dev notes generally
include a description of the change, the decision that led to this change, and a
description of how developers are supposed to work with that change. Dev notes are
published on Make/Core blog during the beta phase of WordPress release cycle. Publishing
dev notes is particularly important when plugin/theme authors and WordPress developers
need to be aware of those changes.In general, all dev notes are compiled into a 
Field Guide at the beginning of the release candidate phase..

---

## References

 * [WordPress 7.0: iteration issue for Pattern Editing and contentOnly interactivity
   #
   73775
 * Original feature issue [Pattern Editing and contentOnly interactivity #71517](https://github.com/WordPress/gutenberg/issues/71517)
 * [Allow disabling content-only editing for unsynced patterns #75457](https://github.com/WordPress/gutenberg/pull/75457)
 * [How to add content-only editing support to a block (developer.wordpress.org)](https://developer.wordpress.org/news/2024/11/how-to-add-content-only-editing-support-to-a-block/)
 * [Miscellaneous Block Editor Changes in WordPress 6.7](https://make.wordpress.org/core/2024/10/20/miscellaneous-block-editor-changes-in-wordpress-6-7/)

[#7-0](https://make.wordpress.org/core/tag/7-0/), [#dev-notes](https://make.wordpress.org/core/tag/dev-notes/),
[#dev-notes-7-0](https://make.wordpress.org/core/tag/dev-notes-7-0/)

 * [Login to Reply](https://login.wordpress.org/?redirect_to=https%3A%2F%2Fmake.wordpress.org%2Fcore%2F2026%2F03%2F15%2Fpattern-editing-in-wordpress-7-0%2F%23respond&locale=en_US)

 [  ](https://profiles.wordpress.org/ramonopoly/) [ramonopoly](https://profiles.wordpress.org/ramonopoly/)
10:35 pm _on_ March 15, 2026     
Tags: [7-0 ( 53 )](https://make.wordpress.org/core/tag/7-0/),
dev-notes, [dev-notes-7-0 ( 19 )](https://make.wordpress.org/core/tag/dev-notes-7-0/)

# 󠀁[Block Visibility in WordPress 7.0](https://make.wordpress.org/core/2026/03/15/block-visibility-in-wordpress-7-0/)󠁿

As of WordPress 6.9, you can hide any 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. entirely with `blockVisibility: false`
in block metadata. In WordPress 7.0, viewport-based visibility rules give your users
the power to show or hide blocks per device type — desktop, tablet, or mobile — 
without affecting other viewports.

Controls are available in the block toolbar, block inspector 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., and command palette to launch 
the block visibility options modal. In List View, blocks with active visibility 
rules show icons that indicate which viewports they are hidden on.

Note: Blocks hidden by viewport are **rendered in the DOM**. The hiding happens **
in the CSSCSS Cascading Style Sheets..**

That’s different from `blockVisibility: false`. That keeps the block from rendering
in the DOM, thus it can’t ever show on the front end.

## Updated `blockVisibility` metadata structure

The existing hide-everywhere behavior has NOT changed:

    ```language-json
    {
      "metadata": {
        "blockVisibility": false
      }
    }
    ```

But in WordPress 7.0, a new `viewport` key gives you and your 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.-literate users finer control, per breakpoint:

    ```language-json
    {
      "metadata": {
        "blockVisibility": {
          "viewport": {
            "mobile": false,
            "tablet": true,
            "desktop": true
          }
        }
      }
    }
    ```

The `viewport` key is deliberately nested, leaving room for more sources (e.g., 
user role, time-based rules) to come in 7.1 and beyond.

The three supported viewport keys are `mobile`, `tablet`, and `desktop`. In 7.0 
these map to fixed breakpoints, but you can expect configurable breakpoints and `
theme.json` integration in WordPress 7.1 — see [#75707](https://github.com/WordPress/gutenberg/issues/75707).

Here’s how this all looks in serialized block markup:

    ```language-markup
    <!-- wp:paragraph {"metadata":{"blockVisibility":{"viewport":{"mobile":false}}}} -->
    <p>Hidden on mobile.</p>
    <!-- /wp:paragraph -->
    ```

### How to get your theme or 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/](https://wordpress.org/plugins/)󠁿 or can be cost-based plugin from a third-party. ready

Does your theme or plugin generate, transform, or parse block markup server-side?
Then its `blockVisibility` metadata field might now contain a boolean (`false`) 
or an object (`{ viewport: { ... } }`). If your code assumes a scalar value, you’ll
want to update it to handle both forms.

Blocks and patterns that include hardcoded `blockVisibility` metadata will work 
out of the box, and so will your reusable blocks that have visibility rules.

#### If your blocks don’t interact with markup on the server

Then you don’t have to do anything! Viewport visibility is part of the `blockVisibility`
block support and applies automatically. You don’t need a separate opt-in in `block.
json`.

### Coming soon! To a future release near you

Current plans call for configurable breakpoints and `theme.json` integration for
block visibility to land in WordPress 7.1. At that point, you’ll be able to let 
your themes and other products define almost any viewport labels and breakpoints
you need, far beyond the fixed mobile/tablet/desktop defaults. Follow [#75707](https://github.com/WordPress/gutenberg/issues/75707)
for progress.

Props to [@andrewserong](https://profiles.wordpress.org/andrewserong/) and [@marybaum](https://profiles.wordpress.org/marybaum/)
for helping to write this dev notedev note Each important change in WordPress Core
is documented in a developers note, (usually called dev note). Good dev notes generally
include a description of the change, the decision that led to this change, and a
description of how developers are supposed to work with that change. Dev notes are
published on Make/Core blog during the beta phase of WordPress release cycle. Publishing
dev notes is particularly important when plugin/theme authors and WordPress developers
need to be aware of those changes.In general, all dev notes are compiled into a 
Field Guide at the beginning of the release candidate phase..

---

## References

 * [Iteration issue: Block visibility based on screen size (WP 7.0)](https://github.com/WordPress/gutenberg/issues/73776)
 * [Feature discussion: hide blocks based on screen size #72502](https://github.com/WordPress/gutenberg/issues/72502)
 * [Original hide blocks feature (WP 6.9) #50756](https://github.com/WordPress/gutenberg/issues/50756)

[#7-0](https://make.wordpress.org/core/tag/7-0/), [#dev-notes](https://make.wordpress.org/core/tag/dev-notes/),
[#dev-notes-7-0](https://make.wordpress.org/core/tag/dev-notes-7-0/)

 * [Login to Reply](https://login.wordpress.org/?redirect_to=https%3A%2F%2Fmake.wordpress.org%2Fcore%2F2026%2F03%2F15%2Fblock-visibility-in-wordpress-7-0%2F%23respond&locale=en_US)

 [  ](https://profiles.wordpress.org/aaronrobertshaw/) [Aaron Robertshaw](https://profiles.wordpress.org/aaronrobertshaw/)
9:43 pm _on_ March 15, 2026     
Tags: [7-0 ( 53 )](https://make.wordpress.org/core/tag/7-0/),
dev-notes, [dev-notes-7-0 ( 19 )](https://make.wordpress.org/core/tag/dev-notes-7-0/)

# 󠀁[Dimensions Support Enhancements in WordPress 7.0](https://make.wordpress.org/core/2026/03/15/dimensions-support-enhancements-in-wordpress-7-0/)󠁿

WordPress 7.0 expands the Dimensions 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. supports system with three significant
improvements: `width` and `height` are now available as standard block supports 
under `dimensions`, and themes can now define dimension size presets to give users
a consistent set of size options across their site.

---

### Background

Previously, blocks that needed width or height controls implemented them as custom
block attributes with their own editor UIUI User interface. This led to duplicated
code, inconsistent experiences across blocks, and no straightforward way to define
width or height values through Global Styles or `theme.json`. The broader Block 
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. goal is to handle these concerns uniformly through block supports, the same
system that already covers spacing, typography, color, and more.

---

### Width Block Support

**PR: [#71905](https://github.com/WordPress/gutenberg/pull/71905)**

`dimensions.width` is now a first-class block support. Block authors can opt in 
by adding `"width": true` under the `dimensions` key in `block.json`:

    ```language-json
    {
        "supports": {
            "dimensions": {
                "width": true
            }
        }
    }
    ```

Once opted in, the block gains a width input in the Dimensions panel of the block
inspector and in the Styles panel of the Site Editor (under Styles > Blocks > Block
Name). Theme authors can define default width values for specific block types via`
theme.json`:

    ```language-json
    {
        "styles": {
            "blocks": {
                "core/paragraph": {
                    "dimensions": {
                        "width": "300px"
                    }
                }
            }
        }
    }
    ```

Block-level values set in the editor will override the theme defaults, following
the same cascade as other block supports.

Themes can also disable the width control globally using the settings API:

    ```language-json
    {
        "settings": {
            "dimensions": {
                "width": false
            }
        }
    }
    ```

The width support respects the full range of block support configuration options:

    ```language-json
    {
        "supports": {
            "dimensions": {
                "width": true,
                "__experimentalSkipSerialization": true,
                "__experimentalDefaultControls": {
                    "width": false
                }
            }
        }
    }
    ```

---

### Height Block Support

**PR: [#71914](https://github.com/WordPress/gutenberg/pull/71914)**

`dimensions.height` follows the same pattern as width. Block authors opt in via `
block.json`:

    ```language-json
    {
        "supports": {
            "dimensions": {
                "height": true
            }
        }
    }
    ```

Theme authors can set default height values per block in `theme.json`:

    ```language-json
    {
        "styles": {
            "blocks": {
                "core/paragraph": {
                    "dimensions": {
                        "height": "300px"
                    }
                }
            }
        }
    }
    ```

And the support can be disabled theme-wide:

    ```language-json
    {
        "settings": {
            "dimensions": {
                "height": false
            }
        }
    }
    ```

Like the width support, height respects `__experimentalSkipSerialization` and `__experimentalDefaultControls`.

---

### Dimension Size Presets

**PR: [#73811](https://github.com/WordPress/gutenberg/pull/73811)**

Alongside the new width and height supports, themes can now define a set of named
dimension size presets via `theme.json`. These presets appear in the width and height
controls, giving users a consistent palette of sizes to choose from rather than 
requiring manual entry of values each time.

Define presets under `settings.dimensions.dimensionSizes`:

    ```language-json
    {
        "settings": {
            "dimensions": {
                "dimensionSizes": [
                    {
                        "name": "Small",
                        "slug": "small",
                        "size": "240px"
                    },
                    {
                        "name": "Medium",
                        "slug": "medium",
                        "size": "480px"
                    },
                    {
                        "name": "Large",
                        "slug": "large",
                        "size": "720px"
                    }
                ]
            }
        }
    }
    ```

Each preset requires three fields:

| Field | Description | 
| `name` | Human-readable label shown in the UI | 
| `slug` | Machine-readable identifier (used to generate a CSSCSS Cascading Style Sheets. custom property) | 
| `size` | Any valid CSS length value (`px`, `%`, `em`, `rem`, `vw`, `vh`, etc.) |

The presets generate CSS custom properties following the `--wp--preset--dimension-
size--{slug}` naming convention, consistent with other `theme.json` presets.

**Control rendering:** The number of presets defined affects how the control is 
rendered:

 * **Fewer than 8 presets:** A slider control is shown, allowing users to step through
   the preset values.
 * **8 or more presets:** A select list (dropdown) is shown instead to keep the 
   UI manageable.

In both cases, users can still enter a custom value directly.

---

### Backwards Compatibility

These are additive changes. No existing blocks are broken. Blocks that do not opt
in to `dimensions.width` or `dimensions.height` in their `block.json` are unaffected.

Blocks that currently implement custom width or height controls as attributes are
encouraged to evaluate migrating to the new block supports, but this is not required
in WordPress 7.0.

The `dimensionSizes` preset key is new; themes without it simply have no presets
defined, which is the default behavior — users can still enter free-form values 
in the width and height controls.

---

### Summary

| Feature | `block.json` key | `theme.json` settings key | `theme.json` styles key | 
| Width block support | `supports.dimensions.width` | `settings.dimensions.width` | `styles.blocks.{name}.dimensions.width` | 
| Height block support | `supports.dimensions.height` | `settings.dimensions.height` | `styles.blocks.{name}.dimensions.height` | 
| Dimension size presets | — | `settings.dimensions.dimensionSizes` | — |

---

### Further Reading

 * [PR #71905: Add width block support](https://github.com/WordPress/gutenberg/pull/71905)
 * [PR #71914: Add height block support](https://github.com/WordPress/gutenberg/pull/71914)
 * [PR #73811: Add dimension presets](https://github.com/WordPress/gutenberg/pull/73811)
 * [PR #71227: Remove custom width/height controls in favor of block supports](https://github.com/WordPress/gutenberg/pull/71227)_(
   broader context)_
 * [Block Supports API documentation](https://developer.wordpress.org/block-editor/reference-guides/block-api/block-supports/)
 * [theme.json reference](https://developer.wordpress.org/block-editor/reference-guides/theme-json-reference/)

---

Props to [@aaronrobertshaw](https://profiles.wordpress.org/aaronrobertshaw/) and
@ryanwelcher for the width and height block support implementations, [@aaronrobertshaw](https://profiles.wordpress.org/aaronrobertshaw/)
for dimension presets, and [@andrewserong](https://profiles.wordpress.org/andrewserong/)
and [@ramonopoly](https://profiles.wordpress.org/ramonopoly/) for technical review
and proofreading.

[#7-0](https://make.wordpress.org/core/tag/7-0/), [#dev-notes](https://make.wordpress.org/core/tag/dev-notes/),
[#dev-notes-7-0](https://make.wordpress.org/core/tag/dev-notes-7-0/)

 * [Login to Reply](https://login.wordpress.org/?redirect_to=https%3A%2F%2Fmake.wordpress.org%2Fcore%2F2026%2F03%2F15%2Fdimensions-support-enhancements-in-wordpress-7-0%2F%23respond&locale=en_US)

 [  ](https://profiles.wordpress.org/aaronrobertshaw/) [Aaron Robertshaw](https://profiles.wordpress.org/aaronrobertshaw/)
9:43 pm _on_ March 15, 2026     
Tags: [7-0 ( 53 )](https://make.wordpress.org/core/tag/7-0/),
dev-notes, [dev-notes-7-0 ( 19 )](https://make.wordpress.org/core/tag/dev-notes-7-0/)

# 󠀁[Custom CSS for Individual Block Instances in WordPress 7.0](https://make.wordpress.org/core/2026/03/15/custom-css-for-individual-block-instances-in-wordpress-7-0/)󠁿

WordPress 7.0 introduces the ability to add custom CSSCSS Cascading Style Sheets.
directly to individual 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. instances from within the post and site editors. This closes a 
long-standing gap in the block styling system: while Global Styles has supported
block-type-level custom CSS since WordPress 6.2, there was no built-in way to target
a single specific block on a specific page without a multi-step workaround.

**GitHubGitHub GitHub is a website that offers online implementation of git repositories
that can easily be shared, copied and modified by other developers. Public repositories
are free to host, private repositories require a paid subscription. GitHub introduced
the concept of the ‘pull request’ where code changes done in branches by contributors
can be reviewed and discussed before being merged by the repository owner. [https://github.com/](https://github.com/)
issue:** [#56127](https://github.com/WordPress/gutenberg/issues/56127) | **PR:**
[#73959](https://github.com/WordPress/gutenberg/pull/73959)

### The Problem

Previously, applying one-off CSS to a specific block instance required a workaround:
add a custom class name to the block, then write a matching rule in the Site Editor’s
global Custom CSS field. This two-step process was not obvious to most users, and
was entirely unavailable to content editors who lack access to the Site Editor.

Plugins emerged to fill this gap, confirming genuine demand for the feature.

### What Changed

A new `customCSS` block support is registered. It provides a **Custom CSS** input
inside the **Advanced** panel of the block inspector — the same panel that already
contains the “Additional CSS Class(es)” field.

The panel behaves the same way as the block-type custom CSS field in Global Styles:

 * Only CSS declarations are needed — no selector is required.
 * Nested selectors can be written using `&` (e.g., `& a { color: red; }` targets
   anchor tags inside the block).
 * HTMLHTML HyperText Markup Language. The semantic scripting language primarily
   used for outputting content in web browsers. markup in the CSS field is rejected.
 * The field is only visible to users with the `edit_css` capabilitycapability A**
   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)..

### How It Works

#### Storage

Custom CSS is stored in the block’s existing `style` attribute, under the `css` 
key — the same attribute that stores other block-level style overrides:

    ```wp-block-code
    <!-- wp:heading {"level":6,"style":{"css":"color: blue;\n"}} -->
    <h6 class="wp-block-heading has-custom-css">Heading</h6>
    <!-- /wp:heading -->
    ```

#### Frontend Output

At render time, a unique class is generated for the block instance using a hash 
of the block’s content and attributes. The class is applied to the block’s outermost
HTML element alongside a `has-custom-css` marker class:

    ```wp-block-code
    <h6 class="wp-block-heading has-custom-css wp-custom-css-8841bf3c3cc97689d62771455cc88782">
        Heading
    </h6>

    <style id="wp-block-custom-css">
        :root :where(.wp-custom-css-8841bf3c3cc97689d62771455cc88782) {
            color: blue;
        }
    </style>
    ```

The generated stylesheet is registered with a dependency on `global-styles`, ensuring
block instance CSS loads **after** — and can therefore override — both WordPress
defaults and Global Styles block-type CSS.

#### Editor Preview

The custom CSS is also applied live in the editor using a scoped style override,
so changes are reflected immediately without saving.

### Opt-Out

The `customCSS` support is **enabled by default for all blocks**. Block authors 
who need to opt out — for example, blocks that render raw content or have no meaningful
outer element — can disable it in `block.json`:

    ```wp-block-code
    {
        "supports": {
            "customCSS": false
        }
    }
    ```

The following coreCore Core is the set of software required to run WordPress. The
Core Development Team builds WordPress. blocks opt out by default: `core/freeform`,`
core/html`, `core/missing`, `core/more`, `core/nextpage`, `core/shortcode`, and `
core/block` (the Reusable Block wrapper).

### Capability Check

The Custom CSS panel is gated by the `edit_css` capability. Users without it will
not see the field in the block inspector. This is the same capability used to control
access to the Custom CSS field in the CustomizerCustomizer Tool 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. and in Global Styles.

### For PluginPlugin A plugin is a piece of software containing a group of functions that can be added to a WordPress website. They can extend functionality or add new features to your WordPress websites. WordPress plugins are written in the PHP programming language and integrate seamlessly with WordPress. These can be free in the WordPress.org Plugin Directory 󠀁[https://wordpress.org/plugins/](https://wordpress.org/plugins/)󠁿 or can be cost-based plugin from a third-party. and Theme Developers

**No action is required** for most blocks and themes. The support is enabled automatically.

If you maintain a block that should not expose a custom CSS input — because it wraps
raw or opaque content, or because adding a class to its root element would break
its rendering — add `"customCSS": false` to your block’s `supports` in `block.json`.

If you render blocks server-side using `render_callback` or `render` in `block.json`,
the class will be injected into the first HTML element in the rendered output via`
WP_HTML_Tag_Processor`. Ensure your block renders a standard HTML element as its
outermost tagtag A directory in Subversion. WordPress uses tags to store a single
snapshot of a version (3.6, 3.6.1, etc.), the common convention of tags in version
control systems. (Not to be confused with post tags.).

### Further Reading

 * [Issue #56127: Block styles — add block instance custom CSS](https://github.com/WordPress/gutenberg/issues/56127)
 * [PR #73959: Add custom CSS support for individual block instances](https://github.com/WordPress/gutenberg/pull/73959)
 * [Block Supports API reference](https://developer.wordpress.org/block-editor/reference-guides/block-api/block-supports/)

_Props to [@mtias](https://profiles.wordpress.org/mtias/) and _[@glendaviesnz](https://profiles.wordpress.org/glendaviesnz/)_
for the implementation, _[@aaronrobertshaw](https://profiles.wordpress.org/aaronrobertshaw/)
and [@scruffian](https://profiles.wordpress.org/scruffian/)_ for technical review
and proofreading._

[#7-0](https://make.wordpress.org/core/tag/7-0/), [#dev-notes](https://make.wordpress.org/core/tag/dev-notes/),
[#dev-notes-7-0](https://make.wordpress.org/core/tag/dev-notes-7-0/)

 * [Login to Reply](https://login.wordpress.org/?redirect_to=https%3A%2F%2Fmake.wordpress.org%2Fcore%2F2026%2F03%2F15%2Fcustom-css-for-individual-block-instances-in-wordpress-7-0%2F%23respond&locale=en_US)

 [  ](https://profiles.wordpress.org/aaronrobertshaw/) [Aaron Robertshaw](https://profiles.wordpress.org/aaronrobertshaw/)
9:43 pm _on_ March 15, 2026     
Tags: [7-0 ( 53 )](https://make.wordpress.org/core/tag/7-0/),
dev-notes, [dev-notes-7-0 ( 19 )](https://make.wordpress.org/core/tag/dev-notes-7-0/)

# 󠀁[New Block Support: Text Indent (textIndent)](https://make.wordpress.org/core/2026/03/15/new-block-support-text-indent-textindent/)󠁿

WordPress 7.0 introduces a new **`textIndent` 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. support** for typography, allowing blocks
to opt in to `text-indent` CSSCSS Cascading Style Sheets. support. The Paragraph
block is the first coreCore Core is the set of software required to run WordPress.
The Core Development Team builds WordPress. block to adopt this support.

### Background

Text indentation is a standard typographic convention, particularly in long-form
publishing. It has been one of the most-requested typography features for WordPress
blocks since 2021 ([#37462](https://github.com/WordPress/gutenberg/issues/37462)).

With this release, WordPress now supports it natively through a new block support.
No custom CSS required.

### The `textIndent` Block Support

Any block can now declare support for `textIndent` in its `block.json`:

    ```language-json
    {
      "supports": {
        "typography": {
          "textIndent": true
        }
      }
    }
    ```

When a block declares this support, the block editor will show a **Line Indent**
control in the Typography panel of the block 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., and the block’s `style.typography.textIndent`
attribute will be serialised as a `text-indent` CSS property.

This follows the same pattern as other typography block supports such as `letterSpacing`,`
textDecoration`, and `textTransform`.

### Paragraph Block: Selector Behaviour and the `textIndent` Setting

The `textIndent` support has a unique consideration specific to the core Paragraph
block: in traditional typographic conventions of English and some other left-to-
right (LTR) languages, only _subsequent_ paragraphs (those that follow another paragraph)
are typically indented, while the very first paragraph in a sequence is not. In 
some right-to-left (RTL) languages and publishing traditions, such as Arabic and
Hebrew, however, it is common to indent all paragraphs.

To accommodate both conventions, a **`typography.textIndent` setting** controls 
which CSS selector is used when generating the `text-indent` rule. This setting 
is distinct from the style value (the actual indent amount) and applies at the Global
Styles level.

| Setting value | Selector used | Behaviour | 
| `"subsequent"` (default) | `.wp-block-paragraph + .wp-block-paragraph` | Only paragraphs immediately following another paragraph are indented | 
| `"all"` | `.wp-block-paragraph` | All paragraphs are indented |

Note: This selector behaviour is specific to `core/paragraph`. Third-party blocks
that opt in to `textIndent` support will have `text-indent` applied using their 
own block selector, but the subsequent/all switching logic is not currently available
to them.

In Global Styles, an **“Indent all paragraphs”** toggle lets authors switch between
the two modes interactively.

### Configuring via `theme.json`

Themes can enable and configure Line Indent support through `theme.json`.

**Enable the control with default (subsequent) behaviour:**

    ```language-json
    {
      "settings": {
        "typography": {
          "textIndent": true
        }
      }
    }
    ```

**Enable with all-paragraphs mode:**

    ```language-json
    {
      "settings": {
        "typography": {
          "textIndent": "all"
        }
      }
    }
    ```

**Set a default indent value globally for paragraphs:**

    ```language-json
    {
      "settings": {
        "typography": {
          "textIndent": "subsequent"
        }
      },
      "styles": {
        "blocks": {
          "core/paragraph": {
            "typography": {
              "textIndent": "1.5em"
            }
          }
        }
      }
    }
    ```

### Backward Compatibility

This is a new opt-in feature. No existing block behaviour changes unless a block
explicitly declares `"textIndent": true` in its `supports.typography` definition.
There are no breaking changes to existing APIs.

### Further Reading

 * GitHubGitHub GitHub is a website that offers online implementation of git repositories
   that can easily be shared, copied and modified by other developers. Public repositories
   are free to host, private repositories require a paid subscription. GitHub introduced
   the concept of the ‘pull request’ where code changes done in branches by contributors
   can be reviewed and discussed before being merged by the repository owner. [https://github.com/](https://github.com/)
   issue: [#37462](https://github.com/WordPress/gutenberg/issues/37462)
 * Implementing PR: [#74889](https://github.com/WordPress/gutenberg/pull/74889)

---

Props [@aaronrobertshaw](https://profiles.wordpress.org/aaronrobertshaw/) for development.
Props [@wildworks](https://profiles.wordpress.org/wildworks/), [@andrewserong](https://profiles.wordpress.org/andrewserong/),
and [@ramonopoly](https://profiles.wordpress.org/ramonopoly/) for technical reviews
and proofreading.

[#7-0](https://make.wordpress.org/core/tag/7-0/), [#dev-notes](https://make.wordpress.org/core/tag/dev-notes/),
[#dev-notes-7-0](https://make.wordpress.org/core/tag/dev-notes-7-0/)

 * [Login to Reply](https://login.wordpress.org/?redirect_to=https%3A%2F%2Fmake.wordpress.org%2Fcore%2F2026%2F03%2F15%2Fnew-block-support-text-indent-textindent%2F%23respond&locale=en_US)

# Post navigation

[← Older posts](https://make.wordpress.org/core/tag/dev-notes/page/2/?output_format=md)