Building an AI Provider Plugin for WordPress 7: Azure AI Foundry Deep-Dive
Reference implementation: This guide walks through the Azure AI Foundry Connector plugin as a complete, real-world example of integrating a custom AI provider with WordPress 7.0.
Quick Links
Section titled “Quick Links”- Architecture Overview
- 1. Provider Registration (PHP)
- 2. Settings and Configuration
- 3. Connectors Page UI (JavaScript)
- 4. Authentication
- 5. AI Plugin Compatibility (Sentinel Connector)
- 6. Testing
- 7. Common Pitfalls
Architecture Overview
Section titled “Architecture Overview”WordPress 7.0 introduces two systems that work together:
┌─────────────────────────────────────────────────────────────┐│ WP Admin UI ││ Settings → Connectors page (React, script modules) ││ ↕ REST API (/wp/v2/settings) │├─────────────────────────────────────────────────────────────┤│ PHP Backend ││ register_setting('connectors', ...) ││ AiClient::defaultRegistry()->registerProvider() ││ ProviderInterface → Models → HTTP requests │└─────────────────────────────────────────────────────────────┘| Layer | Purpose |
|---|---|
| AI Client SDK | PHP library at wp-includes/php-ai-client/ — defines providers, models, capabilities, and the registry |
| Connectors Page | React admin page at Settings → Connectors — UI for API keys and provider settings |
Your plugin bridges both: register a PHP provider with the AI Client and register a JS connector with the Connectors page.
1. Provider Registration (PHP)
Section titled “1. Provider Registration (PHP)”1.1 Plugin Bootstrap
Section titled “1.1 Plugin Bootstrap”Create the main plugin file with early provider registration:
<?phpnamespace AzureAiFoundry;
use WordPress\AiClient\AiClient;use AzureAiFoundry\Provider\AzureAiFoundryProvider;use AzureAiFoundry\Settings\ConnectorSettings;
define( 'AZURE_AI_FOUNDRY_VERSION', '1.0.0' );define( 'AZURE_AI_FOUNDRY_FILE', __FILE__ );
require_once __DIR__ . '/src/autoload.php';
// Register provider early (priority 5)function register_provider(): void { if ( ! class_exists( AiClient::class ) ) { return; }
$registry = AiClient::defaultRegistry();
if ( ! $registry->hasProvider( AzureAiFoundryProvider::class ) ) { $registry->registerProvider( AzureAiFoundryProvider::class ); }}add_action( 'init', __NAMESPACE__ . '\\register_provider', 5 );Key points:
- Register at
initpriority 5 (before core connector binding at priority 20) - Guard against missing
AiClientclass for backward compatibility - Use
hasProvider()to avoid duplicate registration
1.2 Provider Class
Section titled “1.2 Provider Class”The provider extends AbstractApiProvider and implements four factory methods:
<?phpnamespace AzureAiFoundry\Provider;
use WordPress\AiClient\Providers\ApiBasedImplementation\AbstractApiProvider;use WordPress\AiClient\Providers\DTO\ProviderMetadata;use WordPress\AiClient\Providers\Enums\ProviderTypeEnum;use WordPress\AiClient\Providers\Http\Enums\RequestAuthenticationMethod;
class AzureAiFoundryProvider extends AbstractApiProvider {
/** * Base URL from settings. */ protected static function baseUrl(): string { return rtrim( SettingsManager::instance()->get_endpoint(), '/' ); }
/** * Provider identity — appears in the registry and Connectors page. */ protected static function createProviderMetadata(): ProviderMetadata { return new ProviderMetadata( 'azure-ai-foundry', // Unique slug __( 'Azure AI Foundry', 'azure-ai-foundry' ),// Display name ProviderTypeEnum::cloud(), // cloud | server | client 'https://ai.azure.com/', // Where users get API keys RequestAuthenticationMethod::apiKey() // Auth method ); }
/** * Route to the correct model class based on capability. */ protected static function createModel( ModelMetadata $model_metadata, ProviderMetadata $provider_metadata ): ModelInterface { $capabilities = $model_metadata->getSupportedCapabilities();
foreach ( $capabilities as $capability ) { if ( $capability->isImageGeneration() ) { return new AzureAiFoundryImageGenerationModel( $model_metadata, $provider_metadata ); } if ( $capability->isEmbeddingGeneration() ) { return new AzureAiFoundryEmbeddingModel( $model_metadata, $provider_metadata ); } if ( $capability->isTextToSpeechConversion() ) { return new AzureAiFoundryTextToSpeechModel( $model_metadata, $provider_metadata ); } }
return new AzureAiFoundryTextGenerationModel( $model_metadata, $provider_metadata ); }
// ... createProviderAvailability() and createModelMetadataDirectory()}1.3 Model Metadata Directory
Section titled “1.3 Model Metadata Directory”The directory tells the SDK which models your provider offers:
<?phpclass AzureAiFoundryModelMetadataDirectory implements ModelMetadataDirectoryInterface {
private ?array $cached = null;
public function listModelMetadata(): array { if ( null !== $this->cached ) { return $this->cached; }
// 1. Check for configured model name $model_name = SettingsManager::instance()->get_model_name(); if ( ! empty( $model_name ) ) { $this->cached = $this->buildModelsFromConfig( $model_name ); return $this->cached; }
// 2. Try to discover model from /info endpoint $discovered = $this->discoverModel(); if ( $discovered ) { $this->cached = $discovered; return $this->cached; }
// 3. Fall back to generic entry $this->cached = $this->buildGenericModel(); return $this->cached; }}1.4 Model Class (OpenAI-Compatible)
Section titled “1.4 Model Class (OpenAI-Compatible)”If your API follows the OpenAI chat format, extend the provided base class:
<?phpnamespace AzureAiFoundry\Models;
use WordPress\AiClient\Providers\OpenAiCompatibleImplementation\AbstractOpenAiCompatibleTextGenerationModel;
/** * OpenAI-compatible text generation model. * * The base class handles: * - Parameter building (temperature, max_tokens, etc.) * - Message formatting (system/user/assistant roles) * - Response parsing and streaming * - Tool/function calls */class AzureAiFoundryTextGenerationModel extends AbstractOpenAiCompatibleTextGenerationModel { // Nothing to override — the base class handles everything. // Request URL: {baseUrl}/chat/completions?api-version=...}2. Settings and Configuration
Section titled “2. Settings and Configuration”2.1 Register Settings
Section titled “2.1 Register Settings”All settings must use the 'connectors' group and 'show_in_rest' => true:
<?phpclass ConnectorSettings {
public const string OPTION_API_KEY = 'connectors_ai_azure_ai_foundry_api_key'; public const string OPTION_ENDPOINT = 'connectors_ai_azure_ai_foundry_endpoint'; public const string OPTION_MODEL_NAME = 'connectors_ai_azure_ai_foundry_model_name'; public const string OPTION_CAPABILITIES = 'connectors_ai_azure_ai_foundry_capabilities';
public static function register(): void { // API Key register_setting( 'connectors', self::OPTION_API_KEY, [ 'type' => 'string', 'label' => __( 'Azure AI Foundry API Key', 'azure-ai-foundry' ), 'default' => '', 'show_in_rest' => true, // Required for JS UI 'sanitize_callback' => 'sanitize_text_field', ] );
// Mask the key in REST responses add_filter( 'option_' . self::OPTION_API_KEY, [ __CLASS__, 'mask_api_key' ] );
// Endpoint URL register_setting( 'connectors', self::OPTION_ENDPOINT, [ 'type' => 'string', 'default' => '', 'show_in_rest' => true, 'sanitize_callback' => 'esc_url_raw', ] );
// ... additional settings }
/** * Mask API key: bullets + last 4 chars. */ public static function mask_api_key( mixed $key ): string { if ( ! is_string( $key ) || strlen( $key ) <= 4 ) { return is_string( $key ) ? $key : ''; } return str_repeat( '•', min( strlen( $key ) - 4, 16 ) ) . substr( $key, -4 ); }
/** * Read the real (unmasked) API key. */ public static function get_real_api_key(): string { remove_filter( 'option_' . self::OPTION_API_KEY, [ __CLASS__, 'mask_api_key' ] ); $value = get_option( self::OPTION_API_KEY, '' ); add_filter( 'option_' . self::OPTION_API_KEY, [ __CLASS__, 'mask_api_key' ] ); return (string) $value; }}2.2 Environment Variable Fallback
Section titled “2.2 Environment Variable Fallback”Support configuration via environment variables or wp-config.php constants:
<?phpclass SettingsManager {
public function get_real_api_key(): string { $key = ConnectorSettings::get_real_api_key(); if ( ! empty( $key ) ) { return $key; } return $this->resolve_env( 'AZURE_AI_FOUNDRY_API_KEY' ); }
public function resolve_env( string $name ): string { // Check environment variable $value = getenv( $name ); if ( false !== $value && '' !== $value ) { return (string) $value; }
// Check wp-config.php constant if ( defined( $name ) ) { $const = constant( $name ); if ( is_string( $const ) && '' !== $const ) { return $const; } }
return ''; }}3. Connectors Page UI (JavaScript)
Section titled “3. Connectors Page UI (JavaScript)”3.1 Script Modules vs Classic Scripts
Section titled “3.1 Script Modules vs Classic Scripts”This is the single most common pitfall.
WordPress 7.0 uses two script systems:
| System | Examples | How to Use |
|---|---|---|
| Script Modules | @wordpress/connectors | import { ... } from '...' |
| Classic Scripts | api-fetch, element, i18n, components | window.wp.apiFetch, etc. |
Only @wordpress/connectors is a script module. Everything else must be accessed via window.wp.* globals.
If you declare a classic package as a script module dependency, your module silently fails to load — no error in the console.
3.2 Register and Enqueue the Module
Section titled “3.2 Register and Enqueue the Module”// In your main plugin file
function register_connector_module(): void { wp_register_script_module( 'azure-ai-foundry/connectors', plugins_url( 'build/connectors.js', AZURE_AI_FOUNDRY_FILE ), [ [ 'id' => '@wordpress/connectors', // Only script module dep 'import' => 'dynamic', ], // Do NOT add @wordpress/api-fetch, @wordpress/element, etc. ], AZURE_AI_FOUNDRY_VERSION );}add_action( 'init', __NAMESPACE__ . '\\register_connector_module' );
// Enqueue on both possible Connectors page hooksfunction enqueue_connector_module(): void { wp_enqueue_script_module( 'azure-ai-foundry/connectors' );}add_action( 'options-connectors-wp-admin_init', __NAMESPACE__ . '\\enqueue_connector_module' );add_action( 'connectors-wp-admin_init', __NAMESPACE__ . '\\enqueue_connector_module' );3.3 Webpack Configuration
Section titled “3.3 Webpack Configuration”Output ESM for script modules:
const defaultConfig = require( '@wordpress/scripts/config/webpack.config' );const path = require( 'path' );
// Remove DependencyExtractionWebpackPlugin — not used for script modulesconst plugins = defaultConfig.plugins.filter( ( p ) => p.constructor.name !== 'DependencyExtractionWebpackPlugin');
module.exports = { ...defaultConfig, entry: { connectors: path.resolve( process.cwd(), 'src/js', 'connectors.js' ), }, output: { ...defaultConfig.output, path: path.resolve( process.cwd(), 'build' ), module: true, chunkFormat: 'module', library: { type: 'module' }, }, experiments: { ...defaultConfig.experiments, outputModule: true }, externalsType: 'module', externals: { '@wordpress/connectors': '@wordpress/connectors', }, plugins,};3.4 Connector Component
Section titled “3.4 Connector Component”// Script module import (the only ES module)import { __experimentalRegisterConnector as registerConnector, __experimentalConnectorItem as ConnectorItem,} from '@wordpress/connectors';
// Classic scripts via window globalsconst apiFetch = window.wp.apiFetch;const { useState, useEffect, useCallback, createElement } = window.wp.element;const { __ } = window.wp.i18n;const { Button, TextControl } = window.wp.components;
const el = createElement;
// Option names must match PHP register_setting() callsconst API_KEY_OPTION = 'connectors_ai_azure_ai_foundry_api_key';const ENDPOINT_OPTION = 'connectors_ai_azure_ai_foundry_endpoint';
/** * Custom hook for settings management. */function useAzureSettings() { const [ isLoading, setIsLoading ] = useState( true ); const [ apiKey, setApiKey ] = useState( '' ); const [ endpoint, setEndpoint ] = useState( '' );
// Load settings via REST API const loadSettings = useCallback( async () => { try { const data = await apiFetch( { path: `/wp/v2/settings?_fields=${ API_KEY_OPTION },${ ENDPOINT_OPTION }`, } ); setApiKey( data[ API_KEY_OPTION ] || '' ); setEndpoint( data[ ENDPOINT_OPTION ] || '' ); } finally { setIsLoading( false ); } }, [] );
useEffect( () => { loadSettings(); }, [ loadSettings ] );
// Save API key const saveApiKey = useCallback( async ( newKey ) => { const result = await apiFetch( { path: `/wp/v2/settings?_fields=${ API_KEY_OPTION }`, method: 'POST', data: { [ API_KEY_OPTION ]: newKey }, } ); setApiKey( result[ API_KEY_OPTION ] || '' ); }, [] );
return { isLoading, apiKey, endpoint, setEndpoint, saveApiKey };}
/** * Connector component. */function AzureAiFoundryConnector( { slug, name, description, logo } ) { const { isLoading, apiKey, endpoint, setEndpoint, saveApiKey } = useAzureSettings(); const [ isExpanded, setIsExpanded ] = useState( false );
const isConnected = ! isLoading && apiKey !== '';
if ( isLoading ) { return el( ConnectorItem, { logo: logo || el( AzureIcon ), name, description, actionArea: el( 'span', { className: 'spinner is-active' } ), } ); }
const buttonLabel = isConnected ? __( 'Edit', 'azure-ai-foundry' ) : __( 'Set Up', 'azure-ai-foundry' );
return el( ConnectorItem, { logo: logo || el( AzureIcon ), name, description, actionArea: el( Button, { variant: isConnected ? 'tertiary' : 'secondary', onClick: () => setIsExpanded( ! isExpanded ), }, buttonLabel ), }, isExpanded && el( 'div', null, el( TextControl, { label: __( 'API Key', 'azure-ai-foundry' ), value: apiKey, onChange: saveApiKey, } ), el( TextControl, { label: __( 'Endpoint URL', 'azure-ai-foundry' ), value: endpoint, onChange: setEndpoint, } ) ) );}
// Register with the slug format: {type}/{id}registerConnector( 'ai_provider/azure-ai-foundry', { name: __( 'Azure AI Foundry', 'azure-ai-foundry' ), description: __( 'Connect to Azure AI Foundry Model Inference API.', 'azure-ai-foundry' ), render: AzureAiFoundryConnector,} );3.5 Prevent Core from Overriding Your Connector
Section titled “3.5 Prevent Core from Overriding Your Connector”Starting in RC1, WordPress auto-creates connectors for every registered AI provider. Unregister to prevent conflicts:
/** * Unregister so core doesn't manage our API key (no double-masking, no validation issues). */function unregister_from_connector_registry( \WP_Connector_Registry $registry ): void { if ( $registry->is_registered( 'azure-ai-foundry' ) ) { $registry->unregister( 'azure-ai-foundry' ); }}add_action( 'wp_connectors_init', __NAMESPACE__ . '\\unregister_from_connector_registry' );4. Authentication
Section titled “4. Authentication”4.1 Custom Authentication Header
Section titled “4.1 Custom Authentication Header”Azure uses api-key instead of Authorization: Bearer. Extend the SDK’s authentication class:
<?phpnamespace AzureAiFoundry\Http;
use WordPress\AiClient\Providers\Http\DTO\ApiKeyRequestAuthentication;use WordPress\AiClient\Providers\Http\DTO\Request;
/** * Must extend ApiKeyRequestAuthentication (not just implement the interface) * because the ProviderRegistry validates instanceof the class returned by * RequestAuthenticationMethod::apiKey()->getImplementationClass(). */class AzureAiFoundryRequestAuthentication extends ApiKeyRequestAuthentication {
public function authenticateRequest( Request $request ): Request { return $request->withHeader( 'api-key', $this->getApiKey() ); }}4.2 Wire Up Authentication
Section titled “4.2 Wire Up Authentication”Set authentication after core connector key binding (priority 20):
function setup_authentication(): void { if ( ! class_exists( AiClient::class ) ) { return; }
$api_key = SettingsManager::instance()->get_real_api_key();
if ( ! empty( $api_key ) ) { AiClient::defaultRegistry()->setProviderRequestAuthentication( 'azure-ai-foundry', // Must match provider slug new Http\AzureAiFoundryRequestAuthentication( $api_key ) ); }}add_action( 'init', __NAMESPACE__ . '\\setup_authentication', 30 );5. AI Plugin Compatibility (Sentinel Connector)
Section titled “5. AI Plugin Compatibility (Sentinel Connector)”The WordPress AI plugin (wp-content/plugins/ai/) checks wp_get_connectors() for connectors of type ai_provider that have a non-empty API-key option. Because your plugin unregisters its visible connector (§3.5), the AI plugin can’t see it. The fix is a sentinel connector — a hidden, internal connector whose only purpose is signalling “this provider is configured”.
5.1 Define Sentinel Constants
Section titled “5.1 Define Sentinel Constants”define( 'AZURE_AI_FOUNDRY_AI_PLUGIN_SENTINEL_ID', 'azure_ai_foundry_status' );define( 'AZURE_AI_FOUNDRY_AI_PLUGIN_SENTINEL_OPTION', 'connectors_ai_azure_ai_foundry_status_api_key' );The option name must follow the pattern connectors_ai_{sentinel_id}_api_key — this is the option the AI plugin looks up.
5.2 Register the Sentinel in wp_connectors_init
Section titled “5.2 Register the Sentinel in wp_connectors_init”After unregistering your real connector, register the hidden sentinel:
function unregister_from_connector_registry( \WP_Connector_Registry $registry ): void { // Remove the auto-created connector. if ( $registry->is_registered( 'azure-ai-foundry' ) ) { $registry->unregister( 'azure-ai-foundry' ); }
// Register a hidden sentinel so the AI plugin detects us. if ( ! $registry->is_registered( AZURE_AI_FOUNDRY_AI_PLUGIN_SENTINEL_ID ) ) { $registry->register( AZURE_AI_FOUNDRY_AI_PLUGIN_SENTINEL_ID, [ 'name' => __( 'Azure AI Foundry Status', 'azure-ai-foundry' ), 'description' => __( 'Internal compatibility connector for AI plugin detection.', 'azure-ai-foundry' ), 'type' => 'ai_provider', 'authentication' => [ 'method' => 'api_key', ], ] ); }}add_action( 'wp_connectors_init', __NAMESPACE__ . '\\unregister_from_connector_registry' );5.3 Sync the Sentinel Option
Section titled “5.3 Sync the Sentinel Option”Toggle the sentinel option based on whether the provider is actually configured. Run at init priority 35 (after authentication setup at 30):
function sync_ai_plugin_credential_sentinel(): void { $has_api_key = '' !== SettingsManager::instance()->get_real_api_key(); $has_endpoint = '' !== SettingsManager::instance()->get_endpoint(); $current = get_option( AZURE_AI_FOUNDRY_AI_PLUGIN_SENTINEL_OPTION, '' );
if ( $has_api_key && $has_endpoint ) { if ( '1' !== $current ) { update_option( AZURE_AI_FOUNDRY_AI_PLUGIN_SENTINEL_OPTION, '1' ); } return; }
if ( '' !== $current ) { delete_option( AZURE_AI_FOUNDRY_AI_PLUGIN_SENTINEL_OPTION ); }}add_action( 'init', __NAMESPACE__ . '\\sync_ai_plugin_credential_sentinel', 35 );Tip: If your provider doesn’t require an API key (e.g., a local server like exo), trigger the sentinel on endpoint alone.
5.4 Hide the Sentinel from the Connectors Page
Section titled “5.4 Hide the Sentinel from the Connectors Page”Filter the sentinel out of the script module data so it doesn’t appear as a duplicate entry:
function filter_connector_script_data( array $data ): array { if ( isset( $data['connectors'][ AZURE_AI_FOUNDRY_AI_PLUGIN_SENTINEL_ID ] ) ) { unset( $data['connectors'][ AZURE_AI_FOUNDRY_AI_PLUGIN_SENTINEL_ID ] ); } return $data;}add_filter( 'script_module_data_options-connectors-wp-admin', __NAMESPACE__ . '\\filter_connector_script_data' );add_filter( 'script_module_data_connectors-wp-admin', __NAMESPACE__ . '\\filter_connector_script_data' );5.5 Summary Flow
Section titled “5.5 Summary Flow”┌─────────────────────────────────────────────────────┐│ wp_connectors_init ││ 1. Unregister 'azure-ai-foundry' (prevent core UI) ││ 2. Register 'azure_ai_foundry_status' (sentinel) │├─────────────────────────────────────────────────────┤│ init @ priority 35 ││ 3. Sync sentinel option → '1' when configured │├─────────────────────────────────────────────────────┤│ script_module_data_*-connectors-wp-admin ││ 4. Hide sentinel from Connectors UI │├─────────────────────────────────────────────────────┤│ AI plugin reads wp_get_connectors() ││ 5. Finds ai_provider with non-empty api_key → ✅ │└─────────────────────────────────────────────────────┘Without the sentinel, the AI plugin shows: “The AI plugin requires a valid AI Connector to function properly.”
6. Testing
Section titled “6. Testing”6.1 Vitest Configuration
Section titled “6.1 Vitest Configuration”import { defineConfig } from 'vitest/config';import path from 'path';
export default defineConfig( { resolve: { alias: { '@wordpress/connectors': path.resolve( __dirname, 'tests/js/__mocks__/@wordpress/connectors.js' ), }, }, test: { globals: true, environment: 'jsdom', include: [ 'tests/js/**/*.test.js' ], setupFiles: [ 'tests/js/setup-globals.js' ], },} );6.2 Mock the Connectors Module
Section titled “6.2 Mock the Connectors Module”// tests/js/__mocks__/@wordpress/connectors.jsexport const __experimentalRegisterConnector = vi.fn();export const __experimentalConnectorItem = ( { children } ) => children ?? null;export const __experimentalDefaultConnectorSettings = ( { children } ) => children ?? null;6.3 Setup Globals
Section titled “6.3 Setup Globals”import React from 'react';
window.wp = { apiFetch: vi.fn( () => Promise.resolve( {} ) ), element: { useState: React.useState, useEffect: React.useEffect, useCallback: React.useCallback, createElement: React.createElement, }, i18n: { __: vi.fn( ( str ) => str ) }, components: { Button( { children, ...props } ) { return React.createElement( 'button', props, children ); }, TextControl( props ) { return React.createElement( 'input', { type: 'text', value: props.value || '', onChange: ( e ) => props.onChange?.( e.target.value ), } ); }, },};6.4 Test the AI Client Integration
Section titled “6.4 Test the AI Client Integration”// Test in WP-CLI or custom page:use WordPress\AiClient\AiClient;
$result = AiClient::prompt( 'Explain gravity in one sentence.' ) ->usingProvider( 'azure-ai-foundry' ) ->generateTextResult();
echo $result->getText();7. Common Pitfalls
Section titled “7. Common Pitfalls”Provider not appearing on Connectors page?
Section titled “Provider not appearing on Connectors page?”- Check your provider ID matches
/^[a-z0-9_-]+$/(lowercase, digits, underscores, hyphens) - Verify your script module is registered (check Browser DevTools → Network for
<script type="importmap">) - Confirm you’re hooking both
options-connectors-wp-admin_initandconnectors-wp-admin_init
Custom UI replaced by generic API-key input?
Section titled “Custom UI replaced by generic API-key input?”Core auto-registration is overwriting your connector. Unregister from the connector registry:
add_action( 'wp_connectors_init', function( $registry ) { if ( $registry->is_registered( 'azure-ai-foundry' ) ) { $registry->unregister( 'azure-ai-foundry' ); }} );API key disappears after saving?
Section titled “API key disappears after saving?”RC1 validates keys on save by calling isProviderConfigured(). If your provider needs additional config (endpoint URL, etc.) before validation works, the key is silently reverted. Fix: Unregister from the connector registry.
API key shows as all bullets (no last 4 chars)?
Section titled “API key shows as all bullets (no last 4 chars)?”Double-masking: both core and your option_ filter masked the key. Fix: Unregister from the connector registry, or remove your option_ filter.
AI plugin says “requires a valid AI Connector”?
Section titled “AI plugin says “requires a valid AI Connector”?”You’re missing the sentinel connector. See §5 AI Plugin Compatibility. The AI plugin looks for ai_provider-type connectors with a non-empty API-key option — your custom UI connector is invisible after unregistering.
get_option() returns empty despite register_setting default?
Section titled “get_option() returns empty despite register_setting default?”When you call get_option( 'my_option', '' ) with an explicit fallback, WordPress skips the default registered via register_setting(). Call without or with false:
// ❌ Suppresses the registered default$val = get_option( 'connectors_ai_my_endpoint', '' );
// ✅ Uses the registered default from register_setting()$val = get_option( 'connectors_ai_my_endpoint' );This is especially relevant in sentinel sync functions where the setting may never be explicitly saved to the database.
”No models found that support text_generation for this prompt”?
Section titled “”No models found that support text_generation for this prompt”?”- Your
listModelMetadata()returns an empty array (check API key/endpoint) - A caller used an option your model doesn’t declare (e.g.,
->usingWebSearch()) - Missing
outputModalitiesoption in yourSupportedOptionlist - Missing multimodal
inputModalitieswhen caller uses->with_file()
API Reference
Section titled “API Reference”Provider ID Format
Section titled “Provider ID Format”Provider IDs must match /^[a-z0-9_-]+$/:
- Lowercase letters, digits, underscores, hyphens
- Used in
ProviderMetadata,registerConnector(),setProviderRequestAuthentication(), andusingProvider()
Capabilities
Section titled “Capabilities”CapabilityEnum::textGeneration() // GPT-style textCapabilityEnum::imageGeneration() // DALL·E-style imagesCapabilityEnum::embeddingGeneration() // Vector embeddingsCapabilityEnum::textToSpeechConversion() // TTSCapabilityEnum::chatHistory() // Multi-turn conversationProvider Types
Section titled “Provider Types”ProviderTypeEnum::cloud() // Remote API (most common)ProviderTypeEnum::server() // Self-hosted (Ollama, llama.cpp)ProviderTypeEnum::client() // Browser-based (WebLLM)WordPress Version Compatibility
Section titled “WordPress Version Compatibility”| Version | Notable Changes |
|---|---|
| Beta 3 | Connectors page moved to options-connectors.php. Hook both variants. |
| Beta 6 | Core binds connector API keys at init priority 20. Use priority 30 for custom auth. |
| RC1 | ConnectorItem prop renamed icon → logo. Core validates keys on save. Unregister from connector registry for custom UI. |
| RC2 | Provider ID now accepts hyphens (/^[a-z0-9_-]+$/). |
Further Reading
Section titled “Further Reading”- Azure AI Foundry Model Inference API
- WordPress AI Client SDK
- Make/Core blog — WordPress development updates