Lesson 1’s gotcha said a system instruction isn’t a security boundary. This lesson is
about the two things that actually are closer to one: wp_ai_client_prevent_prompt, a
real filter that can stop a prompt from ever being sent, and prompt-level “do not”
instructions, which are worth using but need to be understood for what they really are,
a strong nudge, not an enforceable rule. Knowing which kind of control you’re reaching
for, and when neither one is actually enough, is what this lesson covers.
Course 1’s wp_ai_client_prompt() basics and Lesson 1’s system instruction patterns
from this course. No new setup is required, this lesson adds one new hook on top of
what you already have working.
Step 1: Two different kinds of guardrail
A prompt-level constraint, something like “do not include personal data in your
response,” is read by the model and followed most of the time, but it’s ultimately a
request, not a rule the model is mechanically incapable of breaking. A code-level
guardrail runs in your own PHP, before or around the call, and doesn’t depend on the
model cooperating at all. wp_ai_client_prevent_prompt is the code-level option, it can
stop a prompt from ever leaving your server, deterministically, every time the condition
you write is met.
Step 2: wp_ai_client_prevent_prompt in practice
The filter receives a boolean $prevent value and the WP_AI_Client_Prompt_Builder
instance representing the prompt about to be sent. Returning true blocks it entirely.
A direct, common use is restricting AI features to trusted roles:
// File: my-plugin/class-ai-guardrails.php
add_filter( 'wp_ai_client_prevent_prompt', function ( $prevent, $builder ) {
if ( ! current_user_can( 'manage_options' ) ) {
return true;
}
return $prevent;
}, 10, 2 );
This runs for every prompt sent through wp_ai_client_prompt() anywhere on the site
once the filter is added, which makes it a good place for a broad, site-wide rule.
Step 3: A narrower, feature-specific guardrail
Blocking by capability is useful, but often you want to block one specific feature under one specific condition, a simple rate limit per user is a common real case, tracked with a transient rather than by inspecting the builder’s internal state:
// File: my-plugin/class-ai-guardrails.php
add_filter( 'wp_ai_client_prevent_prompt', function ( $prevent, $builder ) {
$user_id = get_current_user_id();
$transient_key = 'my_plugin_ai_calls_' . $user_id;
$calls_this_hour = (int) get_transient( $transient_key );
if ( $calls_this_hour >= 20 ) {
return true;
}
set_transient( $transient_key, $calls_this_hour + 1, HOUR_IN_SECONDS );
return $prevent;
}, 10, 2 );
Keeping the condition in your own tracked state, a transient, an option, a flag your
feature already sets, rather than trying to inspect deep internals of $builder, keeps
this guardrail simple and easy to reason about on its own.
Step 4: What happens once a prompt is prevented
Once wp_ai_client_prevent_prompt returns true for a given prompt, the SDK’s own
is_supported_*() methods start returning false for it, which is useful for hiding an
AI-powered button in the UI before a user even tries to use it, and any generate_*()
call on that same builder returns a WP_Error instead of making a request. Always check
for that:
// File: my-plugin/class-ai-guardrails.php
$result = wp_ai_client_prompt()
->with_text( $ticket_body )
->generate_text();
if ( is_wp_error( $result ) ) {
return 'AI drafting is not available for your account right now.';
}
Step 5: Prompt-level “do not” instructions, and their real limits
Alongside code-level blocking, plain constraints inside a system instruction are still worth using, “do not invent a fact not present in the input,” “do not include markdown formatting,” “do not exceed 160 characters.” They reduce how often a model produces something you don’t want. They do not guarantee it never happens. Treat every “do not” instruction as lowering a probability, not closing a door, and pair anything that actually matters, personal data exposure, factual fabrication in published content, with the validation step covered in the next lesson, not with wording alone.
If a requirement genuinely cannot be violated, no destructive action without human
confirmation, no personal data in a public-facing response, back it with a real code
check: a wp_ai_client_prevent_prompt block, a permission check, or output validation
after the call. Wording in a system instruction is a real and useful layer, it is not
the last layer.
Test it: confirm a prevented prompt fails safely
wp-env run cli wp eval '
add_filter( "wp_ai_client_prevent_prompt", function ( $prevent, $builder ) {
return true;
}, 10, 2 );
$result = wp_ai_client_prompt()
->with_text( "Say hello." )
->generate_text();
var_dump( is_wp_error( $result ) );
'
You should see bool(true), confirming the call was blocked before it reached a
provider at all, rather than a provider-side rejection or an unhandled exception.
Recap
wp_ai_client_prevent_prompt is a real, code-level guardrail that can stop a prompt from
ever being sent, filtered on a boolean $prevent and the builder itself, with prevented
calls returning false from is_supported_*() and a WP_Error from generate_*().
Prompt-level “do not” instructions are a useful, complementary layer, but they reduce
unwanted output rather than eliminating it, anything that truly cannot be allowed to
happen needs a code-level check behind the wording, not instead of it.
Resources & further reading
- Introducing the AI Client in WordPress 7.0, make.wordpress.org
- PHP AI Client SDK repository, GitHub