Every example so far has assumed the call to generate_text() or AiClient::input()->generateEmbedding()
just works. In production it won’t, always, an API key gets revoked, a provider has an
outage, a rate limit gets hit during a traffic spike, a network blip times out the
request. None of that is a bug in your plugin, it’s the normal cost of depending on an
external service, and code that doesn’t account for it will eventually show a fatal
error, or worse, a blank result, to a real site visitor. This lesson covers what
actually goes wrong and the WordPress-idiomatic way to check for it.
Lessons 1 through 4. This lesson assumes you’re comfortable with the builder chain and is specifically about what happens when that chain doesn’t succeed.
Step 1: What actually goes wrong
Four categories cover almost everything you’ll hit in practice:
- Missing or invalid API key. No provider configured at all, a key that was revoked, or a key tied to an account with no billing attached.
- Network failure. A timeout, a DNS failure, a provider’s API being temporarily unreachable, none of which have anything to do with your code being correct.
- Provider-side rate limits. Every major provider enforces requests-per-minute or tokens-per-minute limits, and a busy WordPress site can hit them, especially if an AI feature runs on every page load rather than being cached or queued.
- Invalid or unexpected responses. A model returning something that doesn’t match a requested JSON schema, an empty string, or a response your code didn’t anticipate the shape of.
None of these are exotic edge cases, they’re the ordinary operating conditions of calling a third-party HTTP API, the same category of failure you’d plan for calling any external service from WordPress.
Step 2: The WP_Error pattern
The WordPress-core-integrated generator methods follow WordPress’s long-standing
convention: on failure, they return a WP_Error object rather than throwing, and
is_wp_error() is how you check for it, exactly as you would after wp_remote_get()
or any other core function that talks to the network:
// File: my-plugin/class-summary-feature.php
$result = wp_ai_client_prompt( $prompt_text )->generate_text();
if ( is_wp_error( $result ) ) {
error_log( 'AI Client error: ' . $result->get_error_message() );
return '';
}
echo esc_html( $result );
This matters because it’s easy to write echo esc_html( $response ) straight after the
call, the way Course 1’s opening example does for brevity, and never notice that on a
bad day $response isn’t a string at all. Checking is_wp_error() before you use the
result isn’t defensive paranoia, it’s the same discipline WordPress core code already
expects from anything wrapping a network call.
Step 3: Wrapping the call in try/catch too
WP_Error covers failures the SDK anticipated and reports cleanly. It’s not
necessarily the only way a call can fail, and if you’re also touching the standalone
wordpress/php-ai-client package directly (Lesson 4’s batch embedding example, for
instance), its builder classes are documented to throw regular PHP exceptions for
things like invalid arguments or unsupported capabilities. Combining both checks is the
safer default:
// File: my-plugin/class-summary-feature.php
try {
$result = wp_ai_client_prompt( $prompt_text )->generate_text();
if ( is_wp_error( $result ) ) {
throw new RuntimeException( $result->get_error_message() );
}
return $result;
} catch ( Throwable $e ) {
error_log( 'AI Client call failed: ' . $e->getMessage() );
return '';
}
Catching Throwable rather than a narrower type is intentional here, this course
can’t promise you the exact exception class name a given provider integration throws
for a network-level failure underneath the SDK, and a broad catch at this boundary is
the honest, defensive choice when calling any external service, not a sign of sloppy
error handling.
Whatever else your error handling does, it should never let an uncaught exception or an
unchecked WP_Error propagate into a fatal error on a page a real visitor is loading.
An AI-powered feature failing should degrade, hide the feature, show a fallback, log
and move on, never take the rest of the page down with it.
Step 4: Checking support before you even call
Rather than discovering a model doesn’t support a capability only after a failed call, the builder exposes support-checking methods you can call first, letting a feature hide itself gracefully instead of erroring:
// File: my-plugin/class-summary-feature.php
$builder = wp_ai_client_prompt( $prompt_text );
if ( ! $builder->is_supported_for_text_generation() ) {
return '';
}
$result = $builder->generate_text();
This is also the mechanism behind the wp_ai_client_prevent_prompt filter, a real,
core-shipped hook that lets site code conditionally block a specific prompt from being
sent at all:
// File: my-plugin/class-governance.php
add_filter( 'wp_ai_client_prevent_prompt', function ( $prevent, $prompt ) {
if ( str_contains( $prompt, 'confidential' ) ) {
return true;
}
return $prevent;
}, 10, 2 );
When this filter blocks a prompt, no request reaches the provider at all, and the
builder’s own is_supported_*() checks report false, letting your feature hide its UI
rather than attempting a call that was never going to happen. Treat this filter as a
safety and governance hook, not a budget or rate-limiting system on its own, it stops a
specific prompt from being sent, it doesn’t track spend or throttle a request rate for
you.
Test it: force and observe a failure
Temporarily deactivate your provider plugin (or remove its API key), then run:
wp-env run cli wp eval '
$result = wp_ai_client_prompt( "test" )->generate_text();
var_dump( is_wp_error( $result ) );
if ( is_wp_error( $result ) ) {
echo $result->get_error_message() . "\n";
}
'
You should see bool(true) and a real error message, confirming your is_wp_error()
check actually catches the failure state rather than assuming success. Reactivate the
plugin and restore the key afterward.
Recap
Plan for missing keys, network failures, provider rate limits, and malformed responses
as the normal cost of calling an external API, not as rare edge cases. Check
is_wp_error() on every generator call the way you would after wp_remote_get(), wrap
the call in a broad try/catch since the standalone package’s builder classes can
throw exceptions too, and use the is_supported_*() checks and the
wp_ai_client_prevent_prompt filter to let a feature fail gracefully rather than
noisily.
Resources & further reading
- PHP AI Client SDK repository, GitHub
- Introducing the AI Client in WordPress 7.0, make.wordpress.org
- WP_Error class reference, developer.wordpress.org