Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save jonathanbossenger/6ba518c97fd6e26eac32fb252d23462c to your computer and use it in GitHub Desktop.

Select an option

Save jonathanbossenger/6ba518c97fd6e26eac32fb252d23462c to your computer and use it in GitHub Desktop.

Abilities API and Connectors: Developer Guide

WordPress is introducing two new interconnected systems for AI integration: the Abilities API and the Connectors system. Together, they provide a standardized way to define executable WordPress capabilities and manage API credentials for external AI providers.

This guide covers both systems from a developer perspective, including architecture, API reference, and practical examples.

Table of Contents

Overview

The two systems serve complementary roles:

  • Abilities API (@wordpress/abilities) — A registry of discoverable, executable WordPress capabilities. Abilities are structured actions (like "create a post" or "navigate to a URL") with defined input/output schemas, permission checks, and validation. They can be registered on the server (PHP) or client (JavaScript) and are designed to be consumed by AI assistants, plugins, or any automation that needs to discover what WordPress can do.

  • Connectors (@wordpress/connectors) — A centralized system for managing API credentials for external AI service providers. It provides a unified Settings page where users configure API keys once, which are then shared across all plugins. Connectors integrates with WordPress's core AiClient class to authenticate requests to providers like OpenAI, Anthropic, and Google.

Abilities API

Architecture

The Abilities API follows a client-server model with two JavaScript packages:

┌─────────────────────────────────────────────────────┐
│                  WordPress Server                    │
│  (PHP: WP_Ability, WP_Ability_Category)             │
│  REST API: /wp-abilities/v1/                        │
└──────────────────────┬──────────────────────────────┘
                       │ REST
┌──────────────────────▼──────────────────────────────┐
│           @wordpress/core-abilities                  │
│  Fetches server abilities & categories on load       │
│  Creates REST-backed callbacks for server abilities  │
└──────────────────────┬──────────────────────────────┘
                       │ registers into
┌──────────────────────▼──────────────────────────────┐
│             @wordpress/abilities                     │
│  In-memory store (core/abilities)                    │
│  Client-side registration, validation, execution     │
│  @wordpress/data integration                        │
└─────────────────────────────────────────────────────┘

Key design decisions:

  1. Unified store — Both server-registered and client-registered abilities coexist in the same core/abilities data store.
  2. Validation at every layer — Names, categories, input, and output are all validated using JSON Schema (draft-04).
  3. Metadata annotations — Abilities carry metadata (e.g. readonly, destructive, idempotent) that controls both behavior and HTTP method selection for server-side execution.

Packages

Package Store Name Purpose
@wordpress/abilities core/abilities Core client library. Registers, queries, validates, and executes abilities.
@wordpress/core-abilities Integration layer. Fetches server-side abilities/categories via REST API and registers them into the client store with REST-backed callbacks.

Core Concepts

Abilities

An ability is a named, categorized action that WordPress (or a plugin) can perform. Each ability has:

Property Type Required Description
name string Yes Unique identifier in namespace format (e.g. my-plugin/create-post).
label string Yes Human-readable label.
description string Yes Detailed description of what the ability does.
category string Yes Slug of a registered category.
input_schema object No JSON Schema for input validation.
output_schema object No JSON Schema for output validation.
callback function Yes* Execution function. (*Required for client-side; auto-generated for server abilities.)
permissionCallback function No Pre-execution permission check.
meta object No Metadata including annotations.

Ability Categories

Categories organize abilities into logical groups. They must be registered before any abilities that reference them.

Property Type Required Description
slug string Yes Unique identifier (lowercase alphanumeric with dashes).
label string Yes Human-readable label.
description string Yes Description of the category.
meta object No Metadata including annotations.

Naming Conventions

Ability names follow a namespaced format with 2–4 segments:

Pattern: /^[a-z0-9-]+(?:\/[a-z0-9-]+){1,3}$/

Valid:
  my-plugin/my-ability        (2 segments)
  core/posts/find             (3 segments)
  my-plugin/resource/sub/action (4 segments)

Invalid:
  my-ability                  (1 segment — no namespace)
  My-Plugin/My-Ability        (uppercase)
  my_plugin/my_ability        (underscores)

Category slugs use lowercase alphanumeric characters and dashes:

Pattern: /^[a-z0-9]+(?:-[a-z0-9]+)*$/

Valid:   data-retrieval, user-management, block-editor
Invalid: Data_Retrieval, --leading-dash, trailing-dash-

Registering Categories

Register a category before registering abilities that belong to it:

import { registerAbilityCategory } from '@wordpress/abilities';

registerAbilityCategory( 'block-editor', {
	label: 'Block Editor',
	description: 'Abilities for interacting with the WordPress block editor',
} );

// Categories can include optional metadata
registerAbilityCategory( 'custom-category', {
	label: 'Custom Category',
	description: 'A category for custom abilities',
	meta: {
		color: '#ff0000',
	},
} );

To remove a category:

import { unregisterAbilityCategory } from '@wordpress/abilities';

unregisterAbilityCategory( 'block-editor' );

Registering Abilities

Register a client-side ability with a callback function:

import { registerAbility } from '@wordpress/abilities';

registerAbility( {
	name: 'my-plugin/navigate',
	label: 'Navigate to URL',
	description: 'Navigates to a URL within the WordPress admin',
	category: 'navigation',
	input_schema: {
		type: 'object',
		properties: {
			url: { type: 'string' },
		},
		required: [ 'url' ],
	},
	callback: async ( { url } ) => {
		window.location.href = url;
		return { success: true };
	},
} );

Important: The category (navigation in this example) must already be registered before the ability can be registered. If it doesn't exist, an error is thrown.

To remove an ability:

import { unregisterAbility } from '@wordpress/abilities';

unregisterAbility( 'my-plugin/navigate' );

Validation on Registration

The store validates every registration and throws descriptive errors:

  • Name is required and must match the naming pattern.
  • Label and description are required.
  • Category must reference an already-registered category.
  • Callback (if provided) must be a function.
  • Duplicate names are rejected.

Executing Abilities

Use executeAbility to run any registered ability (client or server):

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

try {
	const result = await executeAbility( 'my-plugin/create-item', {
		title: 'New Item',
		content: 'Item content',
	} );
	console.log( 'Result:', result );
} catch ( error ) {
	console.error( 'Execution failed:', error.message );
	// error.code may be:
	//   'ability_permission_denied'
	//   'ability_invalid_input'
	//   'ability_invalid_output'
}

Execution flow:

  1. Look up the ability by name.
  2. Run the permissionCallback (if defined). Throw if denied.
  3. Validate input against input_schema (if defined). Throw if invalid.
  4. Call the ability's callback.
  5. Validate output against output_schema (if defined). Throw if invalid.
  6. Return the result.

For server-side abilities, steps 3 and 5 are handled by the server — the client delegates validation to the REST API.

Using the Data Store

The Abilities API integrates with @wordpress/data for reactive use in React components:

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

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

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

	// Get a specific ability
	const specificAbility = useSelect(
		( select ) =>
			select( abilitiesStore ).getAbility( 'my-plugin/my-ability' ),
		[]
	);

	// Get all categories
	const categories = useSelect(
		( select ) => select( abilitiesStore ).getAbilityCategories(),
		[]
	);

	// Get a specific category
	const dataCategory = useSelect(
		( select ) =>
			select( abilitiesStore ).getAbilityCategory( 'data-retrieval' ),
		[]
	);

	return (
		<div>
			<h2>All Abilities ({ allAbilities.length })</h2>
			<ul>
				{ allAbilities.map( ( ability ) => (
					<li key={ ability.name }>
						<strong>{ ability.label }</strong>: { ability.description }
					</li>
				) ) }
			</ul>
		</div>
	);
}

Available selectors:

Selector Arguments Returns
getAbilities( args? ) { category?: string } Ability[]
getAbility( name ) string Ability | undefined
getAbilityCategories() AbilityCategory[]
getAbilityCategory( slug ) string AbilityCategory | undefined

Input and Output Validation

Abilities can define JSON Schema (draft-04) for both input and output. The built-in validator supports:

  • Type validation (string, number, integer, boolean, array, object, null)
  • Required fields
  • Enums and patterns
  • String formats (email, date-time, uuid, ipv4, ipv6, hostname)
  • Number constraints (minimum, maximum, multipleOf)
  • Array constraints (minItems, maxItems, uniqueItems)
  • Object constraints (minProperties, maxProperties)
  • Combinators (anyOf, oneOf)

Example with schemas:

registerAbility( {
	name: 'my-plugin/create-post',
	label: 'Create Post',
	description: 'Creates a new WordPress post',
	category: 'content-management',
	input_schema: {
		type: 'object',
		properties: {
			title: { type: 'string', minLength: 1 },
			content: { type: 'string' },
			status: {
				type: 'string',
				enum: [ 'draft', 'publish', 'pending' ],
			},
		},
		required: [ 'title' ],
	},
	output_schema: {
		type: 'object',
		properties: {
			id: { type: 'integer' },
			link: { type: 'string' },
		},
		required: [ 'id' ],
	},
	callback: async ( input ) => {
		// Implementation...
		return { id: 123, link: 'https://example.com/?p=123' };
	},
} );

You can also validate values directly:

import { validateValueFromSchema } from '@wordpress/abilities';

const schema = {
	type: 'object',
	properties: {
		email: { type: 'string', format: 'email' },
	},
	required: [ 'email' ],
};

const result = validateValueFromSchema( { email: 'not-an-email' }, schema, 'input' );
// Returns a string error message, or `true` if valid

Permission Callbacks

Abilities can include a permissionCallback that runs before execution. If it returns false, execution is denied with an ability_permission_denied error:

registerAbility( {
	name: 'my-plugin/delete-item',
	label: 'Delete Item',
	description: 'Deletes an item permanently',
	category: 'content-management',
	permissionCallback: async ( input ) => {
		// Check if the current user has permission
		const user = await getCurrentUser();
		return user.capabilities.includes( 'delete_posts' );
	},
	callback: async ( input ) => {
		// Deletion logic
	},
} );

Server-Side Abilities

Server-side abilities are registered in PHP and exposed via the REST API at /wp-abilities/v1/. The @wordpress/core-abilities package handles the integration:

  1. On page load, it fetches all categories from GET /wp-abilities/v1/categories.
  2. It registers each category into the client store.
  3. It fetches all abilities from GET /wp-abilities/v1/abilities.
  4. For each server ability, it creates a REST-backed callback and registers it into the client store.

HTTP method selection for server abilities is based on annotations:

Annotations HTTP Method Use Case
readonly: true GET Read-only queries
destructive: true + idempotent: true DELETE Safe-to-retry deletions
Default POST State-changing operations

REST endpoints for execution:

POST   /wp-abilities/v1/abilities/{name}/run   — Default (state-changing)
GET    /wp-abilities/v1/abilities/{name}/run   — Read-only abilities
DELETE /wp-abilities/v1/abilities/{name}/run   — Destructive + idempotent abilities

For GET/DELETE, input is passed as query parameters. For POST, input is sent in the request body as { input: ... }.

To use @wordpress/core-abilities, simply import it — it auto-initializes:

import '@wordpress/core-abilities';

Abilities API Reference

Functions

Function Signature Description
getAbilities ( args?: { category?: string } ) => Ability[] Returns all abilities, optionally filtered by category.
getAbility ( name: string ) => Ability | undefined Returns a specific ability by name.
getAbilityCategories () => AbilityCategory[] Returns all registered categories.
getAbilityCategory ( slug: string ) => AbilityCategory | undefined Returns a specific category by slug.
registerAbility ( ability: Ability ) => void Registers a client-side ability. Throws on validation failure.
unregisterAbility ( name: string ) => void Unregisters an ability.
registerAbilityCategory ( slug: string, args: AbilityCategoryArgs ) => void Registers a category. Throws on validation failure.
unregisterAbilityCategory ( slug: string ) => void Unregisters a category.
executeAbility ( name: string, input?: any ) => Promise<any> Executes an ability with optional input.
validateValueFromSchema ( value: any, schema: object, param?: string ) => true | string Validates a value against a JSON Schema. Returns true or an error message.

Types

interface Ability {
    name: string;
    label: string;
    description: string;
    category: string;
    input_schema?: Record< string, any >;
    output_schema?: Record< string, any >;
    callback?: ( input: any ) => any | Promise< any >;
    permissionCallback?: ( input?: any ) => boolean | Promise< boolean >;
    meta?: {
        annotations?: {
            clientRegistered?: boolean;
            serverRegistered?: boolean;
            readonly?: boolean;
            destructive?: boolean;
            idempotent?: boolean;
        };
        [ key: string ]: any;
    };
}

interface AbilityCategory {
    slug: string;
    label: string;
    description: string;
    meta?: {
        annotations?: {
            clientRegistered?: boolean;
            serverRegistered?: boolean;
        };
        [ key: string ]: any;
    };
}

interface AbilityCategoryArgs {
    label: string;
    description: string;
    meta?: Record< string, any >;
}

Error Codes

Code When
ability_permission_denied permissionCallback returned false.
ability_invalid_input Input failed schema validation.
ability_invalid_output Output failed schema validation.

Connectors

Connectors Architecture

The Connectors system manages API credentials for external AI service providers through a unified WordPress admin interface.

┌─────────────────────────────────────────────────────┐
│            Settings > Connectors Page                │
│  (routes/connectors-home/)                          │
│  Renders registered connectors from store            │
└──────────────────────┬──────────────────────────────┘
                       │ reads from
┌──────────────────────▼──────────────────────────────┐
│           @wordpress/connectors                      │
│  Store: core/connectors                             │
│  registerConnector() / ConnectorItem component       │
└──────────────────────┬──────────────────────────────┘
                       │ saves to / reads from
┌──────────────────────▼──────────────────────────────┐
│          WordPress REST API + Options                │
│  /wp/v2/settings — API key storage                  │
│  /wp/v2/plugins — Plugin install/activate            │
└──────────────────────┬──────────────────────────────┘
                       │ authenticates
┌──────────────────────▼──────────────────────────────┐
│        WordPress\AiClient\AiClient                   │
│  Provider registry with authenticated requests       │
└─────────────────────────────────────────────────────┘

Key components:

  • @wordpress/connectors package — Client-side store and UI components.
  • routes/connectors-home/ — The admin page route that renders the Connectors settings page.
  • lib/experimental/connectors/ — PHP backend for settings registration, API key masking, validation, and WP AI Client integration.

The Connectors Settings Page

The Connectors page is accessible at Settings > Connectors in the WordPress admin. It displays all registered connectors and allows users to:

  1. Install a required provider plugin (if not installed).
  2. Activate the plugin (if installed but inactive).
  3. Set up the connector by entering an API key.
  4. Edit or remove an existing API key.

The page subtitle reads: "All of your API keys and credentials are stored here and shared across plugins. Configure once and use everywhere."

Default Providers

Three AI providers are registered by default:

Connector Slug Provider Plugin Slug Setting Name Help URL
core/openai OpenAI ai-provider-for-openai connectors_ai_openai_api_key https://platform.openai.com
core/claude Anthropic (Claude) ai-provider-for-anthropic connectors_ai_anthropic_api_key https://console.anthropic.com
core/gemini Google (Gemini) ai-provider-for-google connectors_ai_google_api_key https://aistudio.google.com

Each default connector follows a lifecycle:

  1. Check if the provider plugin is installed via /wp/v2/plugins.
  2. If not installed, show an "Install" button that installs and activates the plugin.
  3. If installed but inactive, show an "Activate" button.
  4. Once active, show a "Set up" button that reveals the API key form.
  5. After saving a valid key, show a "Connected" badge and an "Edit" button.

Registering a Custom Connector

Plugins can register their own connectors to appear on the Connectors settings page:

import {
	__experimentalRegisterConnector as registerConnector,
	__experimentalConnectorItem as ConnectorItem,
	__experimentalDefaultConnectorSettings as DefaultConnectorSettings,
} from '@wordpress/connectors';

registerConnector( 'my-plugin/my-provider', {
	label: 'My AI Provider',
	description: 'Custom AI capabilities powered by My Provider.',
	icon: <MyProviderIcon />,
	render: ( { slug, label, description } ) => (
		<ConnectorItem
			icon={ <MyProviderIcon /> }
			name={ label }
			description={ description }
		>
			<DefaultConnectorSettings
				initialValue=""
				helpUrl="https://my-provider.com/api-keys"
				onSave={ async ( apiKey ) => {
					// Save the API key
					await saveMyApiKey( apiKey );
				} }
				onRemove={ async () => {
					// Remove the API key
					await removeMyApiKey();
				} }
			/>
		</ConnectorItem>
	),
} );

Note: All Connector APIs are currently exported with the __experimental prefix, indicating they may change in future releases.

ConnectorConfig

Property Type Required Description
label string Yes Display name of the connector.
description string Yes Short description of the provider's capabilities.
icon ReactNode No Icon or logo component.
render ( props: ConnectorRenderProps ) => ReactNode No Custom render function for the connector card.

ConnectorRenderProps

Property Type Description
slug string The registered connector slug.
label string The connector label.
description string The connector description.

PHP Backend: API Key Storage and Validation

The PHP backend (in lib/experimental/connectors/) handles:

Settings Registration

API keys are stored as WordPress options, registered via register_setting():

// Settings are registered on the 'init' hook
register_setting( 'connectors', 'connectors_ai_openai_api_key', array(
    'type'              => 'string',
    'label'             => 'OpenAI API Key',
    'description'       => 'API key for the OpenAI AI provider.',
    'default'           => '',
    'show_in_rest'      => true,
    'sanitize_callback' => $sanitize_function,
) );

API Key Masking

When API keys are read from the database, they are automatically masked to show only the last 4 characters:

Original:  sk-proj-abc123def456fj39
Masked:    ••••••••••••••••fj39

This is implemented via option_{$setting_name} filters that apply masking whenever the option is read.

API Key Validation

When saving an API key, the sanitize callback validates it against the provider using the WP AI Client registry:

  1. The key is sanitized with sanitize_text_field().
  2. The key is tested against the provider via AiClient::defaultRegistry().
  3. If validation fails, an empty string is saved (rejecting the key).

The REST API also supports explicit validation when fetching settings: if a stored key fails validation, the response value is replaced with 'invalid_key'.

The WP AI Client Integration

The Connectors system depends on WordPress core's \WordPress\AiClient\AiClient class. This dependency is checked before any connector functionality loads:

// In lib/load.php
if ( class_exists( '\WordPress\AiClient\AiClient' ) ) {
    require __DIR__ . '/experimental/connectors/load.php';
}

On the init hook, stored API keys are passed to the AI Client registry:

$registry = \WordPress\AiClient\AiClient::defaultRegistry();
$registry->setProviderRequestAuthentication(
    $provider_id,
    new \WordPress\AiClient\Providers\Http\DTO\ApiKeyRequestAuthentication( $api_key )
);

This means any plugin or theme that uses the WP AI Client can make authenticated requests to configured providers without needing to manage API keys itself.

Connectors API Reference

JavaScript API (@wordpress/connectors)

Exports (all __experimental prefixed):

Export Type Description
__experimentalRegisterConnector function Register a connector for the settings page.
__experimentalConnectorItem component UI component for rendering a connector card.
__experimentalDefaultConnectorSettings component Default API key input form with save/remove.

ConnectorItem Props

Prop Type Required Description
icon ReactNode No Provider logo/icon.
name string Yes Display name.
description string Yes Short description.
actionArea ReactNode No Buttons/badges in the card header.
children ReactNode No Expanded content (e.g. settings form).

DefaultConnectorSettings Props

Prop Type Default Description
initialValue string '' Pre-populated API key value.
helpUrl string URL to the provider's API key page.
helpLabel string Derived from URL Custom label for the help link.
readOnly boolean false Whether the key is in read-only mode (already saved).
onSave ( apiKey: string ) => void | Promise<void> Called when the user clicks Save.
onRemove () => void Called when the user clicks Remove.

Data Store (core/connectors)

The store uses private APIs (accessed via unlock()):

Selector Returns Description
getConnectors() ConnectorConfig[] All registered connectors.
getConnector( slug ) ConnectorConfig | undefined A specific connector.
Action Description
registerConnector( slug, config ) Register a new connector.

PHP Functions

Function Description
_gutenberg_mask_api_key( $key ) Masks an API key, showing only the last 4 characters.
_gutenberg_is_api_key_valid( $key, $provider_id ) Validates an API key against a provider. Returns true, false, or null.
_gutenberg_get_provider_settings() Returns the configuration for all default connector providers.
_gutenberg_register_default_connector_settings() Registers WordPress settings and filters for connector API keys.
_gutenberg_pass_default_connector_keys_to_ai_client() Passes stored API keys to the WP AI Client registry.
_gutenberg_validate_connector_keys_in_rest() Validates API keys in REST settings responses when explicitly requested.

Note: Functions prefixed with _gutenberg_ are private/internal and will likely be renamed when merged into WordPress core.


How Abilities and Connectors Work Together

The Abilities API and Connectors form a complete AI integration framework:

  1. Connectors handle authentication — users configure API keys for AI providers once, and those credentials are securely stored and shared across all plugins.

  2. The WP AI Client (WordPress core) uses those credentials to make authenticated requests to AI providers.

  3. Abilities define what WordPress can do — plugins register discoverable, validated actions that AI assistants (or other consumers) can find and execute.

  4. AI assistants can query the Abilities registry to discover available actions, understand their input/output contracts, and execute them — all through a standardized API.

Example flow:

User configures OpenAI API key in Settings > Connectors
    ↓
Key is stored in wp_options and passed to WP AI Client registry
    ↓
A plugin registers a "generate-alt-text" ability (server-side)
    ↓
An AI assistant discovers the ability via GET /wp-abilities/v1/abilities
    ↓
The assistant calls POST /wp-abilities/v1/abilities/my-plugin/generate-alt-text/run
    ↓
The server-side ability uses the WP AI Client (with the stored OpenAI key)
    to generate alt text and returns the result

This separation of concerns means plugin developers don't need to build their own credential management UI, and AI consumers have a standardized way to discover and invoke WordPress capabilities.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment