Skip to chapter content

Chapter 21 2 min read

Who Gets to Use AI? — Access Control

Blocking/limiting prompts globally or conditionally

Chapter 20’s endpoint checked one permission for one feature. That works fine for a single endpoint. It doesn’t scale if you’re building several AI features and want one consistent policy across all of them, without repeating the same capability check in every callback you write.

One filter, every prompt

wp_ai_client_prevent_prompt runs before any prompt, from anywhere on the site, actually executes:

add_filter(
    'wp_ai_client_prevent_prompt',
    function ( bool $prevent, WP_AI_Client_Prompt_Builder $builder ): bool {
        // Block prompts for anyone who can't at least edit posts.
        if ( ! current_user_can( 'edit_posts' ) ) {
            return true;
        }
        return $prevent;
    },
    10,
    2
);

Return true and that prompt never runs. Return $prevent unchanged, as the last line does, and you’re not overriding anything else that might have already decided to block it, you’re just adding your own condition on top.

What happens to a blocked prompt

Three things happen, and you’ve already written code that handles all of them:

  • No AI call is attempted.
  • is_supported_*() methods return false.
  • generate_*() methods return a WP_Error.

That’s the real value of this filter. Code written the way Chapter 17 taught it, check support first, handle errors after, already responds correctly to being blocked. You don’t need to add anything special anywhere else.

Seeing it in effect

Create chapter-21-access-control.php inside includes:

<?php
/**
* Chapter 21: Who Gets to Use AI?, Access Control
* Usage: add [ai_course_ch21] to any page or post to see the output.
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // No direct access.
}
add_filter(
'wp_ai_client_prevent_prompt',
function ( bool $prevent, WP_AI_Client_Prompt_Builder $builder ): bool {
// Block prompts for anyone who can't at least edit posts.
if ( ! current_user_can( 'edit_posts' ) ) {
return true;
}
return $prevent;
},
10,
2
);
function ai_course_ch21_access_control() {
$builder = wp_ai_client_prompt( 'Write a one-sentence WordPress tip for a beginner.' );
if ( ! $builder->is_supported_for_text_generation() ) {
return '<p><em>AI features are not available for your account.</em></p>';
}
$tip = $builder->generate_text();
if ( is_wp_error( $tip ) ) {
return 'Could not generate a tip right now: ' . esc_html( $tip->get_error_message() );
}
return '<p>' . wp_kses_post( $tip ) . '</p>';
}
add_shortcode( 'ai_course_ch21', 'ai_course_ch21_access_control' );

Look closely at that shortcode function. It’s the exact same pattern Chapter 17 used, unchanged.

Add [ai_course_ch21] to a page while logged in as an editor or admin, and you get a tip. Log out, or switch to an account without edit_posts, and you get the fallback message instead, not an error, not a broken page, the graceful “not available” branch this code already had, now triggered by the filter instead of a missing provider.

Try it yourself

Extend the filter to something more specific than a flat capability check: a per-user daily limit. Store a count in a transient keyed to the current user ID, increment it inside the filter, and return true once someone’s gone over your limit for the day. The rest of your code, this chapter’s shortcode included, doesn’t need to change at all to respect it.

This book is created with Chapterwright