Make WordPress Core

Opened 3 weeks ago

Closed 2 weeks ago

Last modified 12 days ago

#64791 closed enhancement (fixed)

Connectors: Extensibility API allowing plugins to register custom connector cards

Reported by: gziolo's profile gziolo Owned by: gziolo's profile gziolo
Milestone: 7.0 Priority: normal
Severity: normal Version: trunk
Component: Administration Keywords: has-patch has-unit-tests has-dev-note
Focuses: Cc:

Description (last modified by gziolo)

The Connectors screen needs a formal extensibility API so that third-party plugins can register their own connector cards — including custom UI for OAuth flows, multi-step configuration, or non-API-key authentication methods — without being limited to the default API key input card.

The Connectors screen was introduced in https://core.trac.wordpress.org/ticket/64730 (https://github.com/WordPress/gutenberg/pull/75833). https://github.com/WordPress/gutenberg/pull/76014 added dynamic registration from the WP AI Client PHP registry, so provider plugins using AiClient::defaultRegistry()->registerProvider() with api_key authentication now appear automatically. This was a significant step, but the extensibility story remains incomplete.

Current behavior

  • Only providers with api_key authentication are surfaced on the Connectors screen. All other authentication methods are filtered out.
  • Every connector card renders the same default UI: a text input for the API key, with save/remove actions.
  • Third-party plugins can register a provider in PHP and have it appear on the screen, but they cannot customize how their card looks or behaves.
  • There is no mechanism for plugins to add connector types that go beyond AI providers (e.g., OAuth-based services, webhook configurations, or other integration patterns the Connectors screen could support in the future).

Expected behavior

Plugins should be able to:

  1. Register a connector card with custom UI — for example, an OAuth flow with an "Authorize" button and token status, or a multi-field configuration form.
  2. Register connectors using authentication methods other than API keys — OAuth, app passwords, bearer tokens, etc.
  3. Provide card metadata from PHP without needing to enqueue their own JavaScript for simple cases. The current script_module_data bridge from PHP to JS is a reasonable pattern, but the API should be explicit about what metadata plugins can provide (name, description, icon, credentials URL, authentication type, status).
  4. Inject custom rendering on the JS side when the default card layout is insufficient — via a formal JS API that was marked as experimental initially.

Proposed scope

  • Define a PHP API for registering connector cards with metadata (name, description, icon, credentials URL, authentication type, custom fields).
  • Provide a JS-side API for plugins that need custom card rendering.
  • Ensure the default API key card remains the automatic fallback.
  • Document the extensibility API with examples for both PHP-only and PHP+JS integration patterns.

Change History (30)

#1 @gziolo
3 weeks ago

  • Description modified (diff)

#2 @gziolo
3 weeks ago

  • Owner set to gziolo
  • Status changed from new to assigned

This ticket was mentioned in PR #11175 on WordPress/wordpress-develop by @gziolo.


3 weeks ago
#3

  • Keywords has-patch has-unit-tests added

Trac ticket: https://core.trac.wordpress.org/ticket/64791

## Summary

  • Adds a wp_connectors_settings filter in _wp_connectors_get_connector_settings() so plugins can add, modify, or remove connectors displayed on the Connectors screen.
  • The filter runs after built-in and AI Client registry providers are collected but before setting_name is auto-generated — so connectors added via the filter with type => 'ai_provider' and method => 'api_key' automatically get their setting_name populated.
  • Tightens setting_name generation to require type === 'ai_provider', preventing non-AI connectors from receiving an incorrect connectors_ai_* setting name.

### Use cases

  1. Add a third-party AI provider with API key auth
  2. Add a non-AI connector (email, analytics, storage)
  3. Add a no-auth provider (local/on-device AI)
  4. Add a connector with plugin slug for install/activate UI
  5. Modify description, name, or credentials URL of a built-in provider
  6. Modify auth method (e.g., proxy plugin switches api_key to none)
  7. Remove a provider (enterprise policy, avoid duplicates)
  8. Filter conditionally by role, multisite, license, or feature flag
  9. Reorder connectors (filter runs after ksort)

Follows from the discussion in https://github.com/WordPress/gutenberg/pull/76014#discussion_r2868695097.

## Test plan

  • [x] npm run test:php -- --group connectors passes (20 tests, 98 assertions)
  • [ ] Verify filter can add a new non-AI connector and it appears in the Connectors screen data
  • [ ] Verify filter can remove a built-in connector
  • [ ] Verify filter-added AI provider with api_key auth gets setting_name auto-generated
  • [ ] Verify filter-added non-AI connector with api_key auth does not get setting_name

🤖 Generated with Claude Code

@gziolo commented on PR #11175:


3 weeks ago
#4

It's something I still feel comfortable shipping in WordPress 7.0 if we can agree that it would be a valuable addition. Nearly the same structure gets passed to the client as the filter wp_connectors_settings receives, so it's fine to seal it as a contract.

#5 @gziolo
3 weeks ago

  • Milestone changed from Awaiting Review to 7.0

#6 @gziolo
3 weeks ago

  • Component changed from General to Administration

#7 @cfinke
3 weeks ago

We'd be interested in using this for adding Akismet to the list of connectors; it would probably fall under use case 4. I'm happy to test and offer feedback as the API evolves.

@gziolo commented on PR #11175:


3 weeks ago
#8

@felixarntz Thanks for the thoughtful review! I agree that a registry class with an action hook would be the better pattern here — it's consistent with how Core handles registration elsewhere (register_block_type, register_post_type, wp_register_ability, etc.) and avoids the pitfalls of filters with nested structured data.

I also realized that we already have a way to filter what reaches the frontend via the script_module_data_options-connectors-wp-admin filter, which reduces the need for a filter on the registration side as currently proposed in this PR.

I'm going to explore your proposed approach with WP_Connector_Registry + a wp_connectors_init action. Will update the PR once I have something to show.

@gziolo commented on PR #11175:


3 weeks ago
#9

@felixarntz, I quickly prototyped the idea you outlined. Does it align with what you had in mind?

@gziolo commented on PR #11175:


3 weeks ago
#10

I addressed all the remaining feedback and updated the PR's description to reflect the current version. I also applied additional enhancements to the PHPStan Connector type. If there is no further feedback, I plan to commit the changes tomorrow.

@gziolo commented on PR #11175:


2 weeks ago
#11

I drafted PR for Gutenberg to sync all the changes: https://github.com/WordPress/gutenberg/pull/76364. It's a first pass, and I need to have a closer look at both implementations.

#12 @gziolo
2 weeks ago

  • Resolution set to fixed
  • Status changed from assigned to closed

In 61943:

Connectors: Add connector registry for extensibility

Introduces WP_Connector_Registry class and a wp_connectors_init action hook so plugins can register their own connectors alongside the built-in defaults (Anthropic, Google, OpenAI).

Key changes:

  • WP_Connector_Registry — A final singleton class managing connector registration and lookup, with validation for IDs, required fields, and authentication methods.
  • wp_connectors_init action — Fired during init after built-in connectors are registered. Passes the registry instance so plugins call $registry->register() directly.
  • _wp_connectors_init() — Private function that creates the registry, merges hardcoded defaults with AI Client registry data, registers them, then fires the action.
  • Public read-only functions — wp_is_connector_registered(), wp_get_connector(), wp_get_connectors() for querying the registry after initialization.
  • Logo URL support — Connectors can include an optional logo_url field resolved from plugin directories via _wp_connectors_resolve_ai_provider_logo_url().
  • Timing guards — set_instance() rejects calls after init completes. Registration is only possible during wp_connectors_init.
  • Connector API key settings are now only registered when the provider exists in the AI Client registry.
  • Refactors _wp_connectors_get_connector_settings() to read from the registry via wp_get_connectors().

Developed in https://github.com/WordPress/wordpress-develop/pull/11175

Props gziolo, flixos90, mukesh27, westonruter.
Fixes #64791.

#13 @gziolo
2 weeks ago

The scope for WP 7.0 has been covered, so I closed this ticket. There is additional follow-up work needed, but another ticket will be required, which I will handle soon.

@gziolo commented on PR #11175:


2 weeks ago
#14

As I go through the codebase again, I noticed that we don't need one method anymore after all the refactorings applied:

https://github.com/WordPress/wordpress-develop/blob/876044bec69252bf7831211ded6857bb0df29f6a/src/wp-includes/connectors.php#L356-L360

We can simply apply the sorting before we send the data to JS. I will patch it shortly.

This ticket was mentioned in PR #11227 on WordPress/wordpress-develop by @gziolo.


2 weeks ago
#15

## Summary

  • Removes the private _wp_connectors_get_connector_settings() wrapper, which only called wp_get_connectors() and applied ksort().
  • Callers now use wp_get_connectors() directly.
  • The alphabetical sort is applied in _wp_connectors_get_connector_script_module_data() just before data is passed to JavaScript — the only place where ordering matters.
  • Expands the @return PHPDoc on wp_get_connectors() with the full array shape previously documented on the removed helper.
  • Renames the test class from wpConnectorsGetConnectorSettings to wpGetConnectors, targeting wp_get_connectors() directly.

## Test plan

  • [ ] Run npm run test:php -- --filter=connectors and confirm all 54 tests pass.
  • [ ] Run npm run env:composer -- lint -- src/wp-includes/connectors.php and confirm no PHPCS violations.

🤖 Generated with Claude Code

@gziolo commented on PR #11227:


2 weeks ago
#16

@westonruter, I would appreciate your guidance on how to handle PHPDoc and PHPStan for wp_get_connectors() that wraps the same method from get_all_registered from WP_Connector_Registry.

#17 @gziolo
2 weeks ago

  • Resolution fixed deleted
  • Status changed from closed to reopened

@gziolo commented on PR #11227:


2 weeks ago
#19

@westonruter, thank you for all the insights. I still need to do a round of cleanup to align PHPDoc shapes for the connector array.

@gziolo commented on PR #11227:


2 weeks ago
#20

It's really hard to draw the line of when to include the entire shape of the return value. Maybe it's enough to combine the PHPStan type and a very generic array as @return. The only place where we must document the entire shape is WP_Connectors_Registry::register. That's something we can decide on later, though.

#21 @gziolo
2 weeks ago

  • Resolution set to fixed
  • Status changed from reopened to closed

In 61983:

Connectors: Remove redundant helper and improve PHPDoc

Remove _wp_connectors_get_connector_settings() and inline ksort()
into _wp_connectors_get_connector_script_module_data(). Expand
@return and @phpstan-return array shapes for wp_get_connector()
and wp_get_connectors(). Make logo_url and credentials_url truly
optional. Rename test class to wpGetConnectors.

Developed in https://github.com/WordPress/wordpress-develop/pull/11227.

Follow-up to [61943].

Props gziolo, westonruter.
Fixes #64791.

#22 @gziolo
2 weeks ago

  • Keywords needs-dev-note added

#23 @gziolo
2 weeks ago

I opened a follow-up ticket #64850 to tackle in future releases.

#24 @cfinke
2 weeks ago

Is there documentation outside of the code for how to use this new feature? I've tried implementing it for Akismet's API key (which is maybe premature, since only ai_provider types are supported), and it doesn't seem functional apart from showing the form for entering the API key.

This ticket was mentioned in PR #11244 on WordPress/wordpress-develop by @gziolo.


2 weeks ago
#25

Trac ticket: https://core.trac.wordpress.org/ticket/64791
Follow-up for https://github.com/WordPress/wordpress-develop/pull/11175

## Summary

  • Add file-level overview explaining the Connectors API and how AI providers integrate automatically.
  • Add usage examples and cross-references to public API functions.
  • Document admin UI scope: only ai_provider + api_key connectors get full UI today.
  • Fix code examples to use the correct unregister/register override pattern.

## Test plan

  • [x] Documentation-only change — no functional impact.

🤖 Generated with Claude Code

@gziolo commented on PR #11175:


2 weeks ago
#26

I'm working on enhancements to the code documentation:

@gziolo commented on PR #11244:


2 weeks ago
#27

# Dev Note

# Introducing the Connectors API in WordPress 7.0

WordPress 7.0 introduces the Connectors API — 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 admin UI for configuring AI services.

This post walks through what the Connectors API does, how it works under the hood, and what plugin developers need to know.

## Table of Contents

## 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.org plugin. The system currently focuses on AI providers, but the architecture is designed to support additional connector types in future releases.

WordPress 7.0 ships with three built-in connectors: Anthropic, Google, and OpenAI. These are registered automatically during initialization and appear on the new Settings > Connectors admin screen.

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

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(
        'slug' => 'ai-provider-for-anthropic',
    ),
)

## 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 ProviderRegistry 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 WP AI Client registry 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.

The authentication method (api_key or none) is determined by the provider's metadata in the WP AI Client. For api_key providers, a setting_name is automatically generated following the pattern connectors_ai_{$id}_api_key — the same naming convention used for environment variables and PHP constants (e.g., provider anthropic maps to ANTHROPIC_API_KEY for env/constant lookup).

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 ai_provider 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.slug enables install/activate controls — the screen checks 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, PHP constant, or database) and connection status.

Connectors with other authentication methods or types are stored in the PHP registry and exposed via the script module data, but currently require a client-side JavaScript 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.

### API key source priority

For api_key connectors, the system looks for a key 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

The naming convention follows the pattern {PROVIDER_ID}_API_KEY in CONSTANT_CASE. For example, provider anthropic maps to ANTHROPIC_API_KEY.

## 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:

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

### wp_get_connector()

Retrieves a single connector's data:

$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:

$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:

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 and underscores 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 is scoped to AI providers, but the underlying architecture is designed to grow. Currently, only ai_provider 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 non-AI connector types.

Future releases are expected to:

  • Expand support for additional authentication methods beyond api_key and none.
  • Lift the ai_provider type restriction for the admin screen.
  • Provide a client-side JavaScript registration API for custom connector UI.

When those capabilities land, the wp_connectors_init action will be the primary hook for registering new connector types.

---

_Props to @jorgefilipecosta, @shaunandrews @felixarntz, @jameswlepage, @gziolo and others for contributing to the Connectors API._

_#7-0, #dev-notes_

#28 @gziolo
2 weeks ago

  • Keywords has-dev-note added; needs-dev-note removed

#29 @gziolo
12 days ago

In 62032:

Connectors: Improve inline documentation for the Connectors API

Adds comprehensive PHPDoc to connectors.php and class-wp-connector-registry.php covering API overview, AI provider auto-discovery, admin UI integration, initialization lifecycle, authentication, usage examples, cross-references, and metadata override patterns.

Developed in https://github.com/WordPress/wordpress-develop/pull/11244.

Follow-up to [61943].

See #64791.

Note: See TracTickets for help on using tickets.