How the WordPress 7.0 AI Hard Stop Actually Works (wp_ai_client_prevent_prompt, Explained)

Axtolab · · 6 min read

WordPress 7.0’s AI Client shares one provider API key with every plugin on your site, and core ships no budget, no cap, and no per-plugin limit. What core does ship — quietly, and almost nobody is writing about it — is the mechanism to build all of those: a filter called wp_ai_client_prevent_prompt.

We built a commercial enforcement product on this filter and verified its behavior against a live provider, so this is the documentation we wish had existed. By the end you’ll know exactly how the hard stop works, what a blocked plugin experiences, how to write a crude version yourself in ten lines, and where the ten-line version stops being enough.

Where the filter sits in the pipeline

Every AI call through the native stack — whether a plugin calls wp_ai_client_prompt() directly or builds a prompt fluently through the client — passes through the AI Client’s prompt builder before anything is dispatched to OpenAI, Anthropic, or Google. That builder consults one filter:

apply_filters( 'wp_ai_client_prevent_prompt', false, $builder );

Return false (the default) and the request proceeds to the provider. Return true and the pipeline stops inside WordPress — no HTTP request is created, nothing leaves your site, and your provider never sees the call. We verified this against a live Gemini account: calls blocked by this filter simply never appear in the provider’s logs. That matters because it means blocking happens before billing, not after.

The second argument is a clone of the prompt builder, so your callback can inspect what’s being asked for without being able to mutate the real request.

The detail everyone misses: it fires for capability checks too

The filter doesn’t only gate generation calls. It also fires when plugins ask whether AI is available at all — the is_supported_*() capability checks. This is an elegant design decision with a practical consequence:

  • A generation call that’s blocked returns a standard WP_Error with code prompt_prevented.
  • A capability check while blocking is active returns false — meaning AI is “not supported.”

So a well-built plugin that checks capability before showing its AI features will simply hide its AI UI while a block is active, exactly as if no provider were configured. It doesn’t error; it degrades. And a badly-built plugin that skips the capability check and fires generations blindly gets a WP_Error it must already handle, because that’s the same error surface as “no provider configured.” Either way, the plugin cannot spend.

This is why enforcement at this layer beats every alternative. Provider-side spending caps kill everything at once, including the plugin that was behaving. HTTP-layer interception fights WordPress and breaks on every transport change. The filter is core’s own sanctioned off-switch, per call, with graceful degradation built into the contract.

Build a crude kill switch yourself (10 lines)

Here’s the honest part: if all you want is “stop all AI calls right now,” you don’t need a plugin. Drop this in an mu-plugin:

<?php
/**
 * Plugin Name: Emergency AI kill switch
 */
add_filter( 'wp_ai_client_prevent_prompt', function ( $prevent, $builder ) {
	if ( get_option( 'my_ai_kill_switch' ) ) {
		return true; // Block every AI Client call sitewide.
	}
	return $prevent; // Respect blocks set by earlier filters.
}, 10, 2 );

Flip the option, and every AI call on the site stops before the provider is touched. That’s the whole mechanism. Core gave everyone the breaker — it just didn’t wire it to anything.

Where the 10-line version stops being enough

The moment you want budget enforcement rather than a panic button, three hard problems appear:

1. The filter has no idea who’s calling. To block one plugin’s calls — the one burning your budget — while everything else keeps working, you need attribution: which plugin initiated this prompt? Core doesn’t pass that in. The reliable technique is inspecting the call stack at filter time and walking to the first file inside a plugin or theme directory (the same approach Query Monitor uses). It works, with sharp edges: cron-initiated calls attribute to the plugin whose callback ran, and — a bug we found in our own testing — an enforcement plugin’s filter callback sits in the stack itself, so naive attribution resolves to you, self-excludes, and silently never blocks anything. You have to skip your own frames.

2. The filter has no idea what’s been spent. “Block when the plugin exceeds $10 this month” requires a running, per-plugin spend ledger — which means recording every call’s token usage (from the AI Client’s wp_ai_client_after_generate_result event), pricing it, and querying month-to-date at decision time, fast enough to sit in the hot path of every AI call. Core’s own AI Request Logs are view-only and don’t attribute per plugin.

3. Blocking silently is how you lose trust. A budget that trips without telling anyone looks like a broken site. Real enforcement needs threshold warnings before the stop (we send at 50/75/100% of each budget), a notice when blocking starts, an audit trail of exactly which calls were refused and why, and throttling so an incident doesn’t flood your inbox.

That’s the gap between the mechanism (free, in core, documented above) and a product. Our split: the free AI Spend Monitor solves the ledger — per-plugin attribution, exact token counts (verified line-for-line against core’s request logs), estimated cost, history, CSV. The paid AI Spend Governance sits on wp_ai_client_prevent_prompt with per-plugin and sitewide monthly budgets, the threshold alerts, a configurable hard-stop percentage, the audit trail of blocked calls, and a sitewide kill switch — $39/yr early pricing.

FAQ for implementers

Does returning true throw, or fail gracefully? Gracefully: generations get WP_Error('prompt_prevented'), capability checks return false. No exceptions, no fatals.

Can another plugin un-block my block? Filters compose in priority order; a later callback could return false and override you. In practice nothing in the ecosystem does this today, but it’s the standard WordPress filter contract — there is no “final say.”

Does it cover plugins that bring their own API key? No — and nothing at the WordPress layer can. A plugin calling a provider directly with its own key bypasses the AI Client entirely. (The free Monitor’s absence-of-evidence test is the quickest way to find those plugins: trigger their AI features and see if they appear in the dashboard.)

Does blocking cost anything? No tokens, no provider charge — the request never leaves the site. The only cost is your filter callback’s own execution time, which is why spend lookups in the hot path need caching.


The deep-reference version of this article — evaluation order, error surfaces, and the attribution edge cases — lives in our documentation. If you’re building on the filter and hit something undocumented, tell us — we’ve probably hit it too.

wordpress-7 wp_ai_client_prevent_prompt ai-client hard-stop ai-budget developers