Reliability

Handling Errors and Building Fallbacks

⏱ 16 min

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.

What you'll learn in this lesson
The four common failure modes
Missing or invalid API keys, network failures, provider-side rate limits, and malformed responses.
The WP_Error return pattern
How generator methods signal failure, and the is_wp_error() check that catches it.
Wrapping calls defensively
try/catch around the call itself, since both WP_Error returns and thrown exceptions are possible.
Checking support before calling
is_supported_for_text_generation() and related checks, so a feature can hide itself gracefully.
Prerequisites

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.

Never let an AI call take down the whole request

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

← Generating Embeddings Provider-Switching for Cost and Quality →