releases.shpreview

Abilities API gains pre-execution, input, permission, and result filters

v7.1

4 featuresThis release4 featuresNew capabilitiesAI-tallied from the release notes
From the original release noteView original ↗

WordPress 7.1 introduces four filters that allow plugins to customise the execution lifecycle of abilities registered with the Abilities API.

The Abilities API previously provided the wp_before_execute_ability and wp_after_execute_ability actions. These actions are useful for observing execution, but they cannot change its behaviour.

The new filters allow developers to:

  • Short-circuit ability execution.
  • Transform normalised input.
  • Apply additional authorisation rules.
  • Transform or recover an execution result.

These changes were introduced in Trac ticket #64989 and changeset #62397.

  1. Updated execution lifecycle
  2. Short-circuiting execution with wp_pre_execute_ability
    1. Temporarily disabling an ability
  3. Transforming input with wp_ability_normalize_input
    1. Adding contextual input
  4. Filtering permission results with wp_ability_permission_result
    1. Applying an additional authorisation policy
  5. Transforming results with wp_ability_execute_result
    1. Removing internal response data
    2. Recovering from selected execution failures
  6. New WP_Filter_Sentinel class
  7. Backward compatibility
  8. Summary

Updated execution lifecycle

The filters are applied in the following order:

wp_pre_execute_ability

        ├── short-circuit when an override is returned

WP_Ability::normalize_input()

wp_ability_normalize_input

WP_Ability::validate_input()

WP_Ability::check_permissions()

wp_ability_permission_result

wp_before_execute_ability

Registered execute callback

wp_ability_execute_result

WP_Ability::validate_output()

wp_after_execute_ability

Return result

Input and output transformations occur before their respective schema-validation steps. Transformed values must therefore continue to satisfy the ability’s registered schemas.

The exception is wp_pre_execute_ability, which bypasses the rest of the pipeline completely.

Short-circuiting execution with wp_pre_execute_ability

The wp_pre_execute_ability filter runs at the beginning of WP_Ability::execute(), before input normalization, validation or permission checks.

/**
 * Filters whether to short-circuit ability execution.
 *
 * @param mixed      $pre          Precomputed result. Return it unchanged to
 *                                 continue normal execution.
 * @param string     $ability_name Name of the ability.
 * @param mixed      $input        Raw input passed to execute().
 * @param WP_Ability $ability      Ability instance.
 */
apply_filters(
    'wp_pre_execute_ability',
    $pre,
    $ability_name,
    $input,
    $ability
);

Returning $pre unchanged allows execution to continue. Returning any other value short-circuits execution and returns that value directly to the caller.

The filter uses a unique internal sentinel as its default. This means that any PHP value—including null, false, or an object—can be used as a legitimate short-circuit result.

The filter can be used for caching, rate limiting, maintenance mode, approval workflows and test mocking.

Temporarily disabling an ability

The filter can short-circuit selected abilities during maintenance without running input validation, permission checks, or the registered callback:

add_filter(
    'wp_pre_execute_ability',
    function ( $pre, $ability_name, $input, $ability ) {
        if ( 'my-plugin/sync-catalog' !== $ability_name ) {
            return $pre;
        }

        if ( ! get_option( 'my_plugin_maintenance_mode', false ) ) {
            return $pre;
        }

        return new WP_Error(
            'ability_temporarily_unavailable',
            __( 'This operation is temporarily unavailable due to maintenance.', 'my-plugin' ),
            array(
                'status' => 503,
            )
        );
    },
    10,
    4
);

Returning $pre unchanged continues normal execution. When maintenance mode is enabled, the WP_Error is returned immediately, and the remaining ability pipeline is bypassed.

Because this filter runs before permission checks and validation, it should make only narrow decisions that do not depend on validated input or the current ability authorisation result.

Transforming input with wp_ability_normalize_input

The wp_ability_normalize_input filter runs inside WP_Ability::normalize_input(), after the method has applied any defaults declared by the input schema.

/**
 * Filters normalized ability input.
 *
 * @param mixed      $input        Normalized input.
 * @param string     $ability_name Name of the ability.
 * @param WP_Ability $ability      Ability instance.
 */
apply_filters(
    'wp_ability_normalize_input',
    $input,
    $ability_name,
    $ability
);

This filter can be used to:

  • Add defaults that cannot be expressed through JSON Schema.
  • Normalize incoming values.
  • Enrich an AI prompt.
  • Inject caller or execution-context metadata.

Adding contextual input

add_filter(
    'wp_ability_normalize_input',
    function ( $input, $ability_name, $ability ) {
        if ( 'my-plugin/process-content' !== $ability_name ) {
            return $input;
        }

        if ( ! is_array( $input ) ) {
            $input = array();
        }

        $input['requesting_user_id'] = get_current_user_id();
        $input['site_url']           = home_url();

        return $input;
    },
    10,
    3
);

The transformed input is subsequently checked against the ability’s input_schema.

Returning a WP_Error stops execution before input validation, permission checks and the registered callback:

add_filter(
    'wp_ability_normalize_input',
    function ( $input, $ability_name ) {
        if ( 'my-plugin/process-content' !== $ability_name ) {
            return $input;
        }

        if ( my_plugin_rate_limit_exceeded() ) {
            return new WP_Error(
                'ability_rate_limit_exceeded',
                __( 'The ability rate limit has been exceeded.', 'my-plugin' ),
                array( 'status' => 429 )
            );
        }

        return $input;
    },
    10,
    2
);

When execution occurs through the Abilities REST API, a WP_Error returned during normalization is now propagated by the REST controller. It defaults to HTTP status 400 unless the error specifies another status, such as 422 or 429.

Filtering permission results with wp_ability_permission_result

The wp_ability_permission_result filter runs inside WP_Ability::check_permissions(), after the registered permission_callback has executed.

/**
 * Filters the result of an ability permission check.
 *
 * @param bool|WP_Error $permission   Result from permission_callback.
 * @param string        $ability_name Name of the ability.
 * @param mixed         $input        Input used for the permission check.
 * @param WP_Ability    $ability      Ability instance.
 */
apply_filters(
    'wp_ability_permission_result',
    $permission,
    $ability_name,
    $input,
    $ability
);

The filter may return:

  • true to permit execution.
  • false to deny execution.
  • A WP_Error to deny execution with a specific error and message.

Any other return value is converted to false.

Because the filter is part of check_permissions(), it also applies when permission checks are performed independently of execute(), including REST API and WP-CLI integrations.

Applying an additional authorisation policy

add_filter(
    'wp_ability_permission_result',
    function ( $permission, $ability_name, $input, $ability ) {
        if ( 'my-plugin/delete-records' !== $ability_name ) {
            return $permission;
        }

        // preserve both false and WP_Error results
        if ( false === $permission || is_wp_error( $permission ) ) {
            return $permission;
        }

        if ( ! current_user_can( 'manage_options' ) ) {
            return new WP_Error(
                'ability_additional_permission_required',
                __( 'This operation requires administrator access.', 'my-plugin' )
            );
        }

        return true;
    },
    10,
    4
);

Plugins should exercise particular care with this filter because returning true can override a denial from the ability’s original permission_callback.

Transforming results with wp_ability_execute_result

The wp_ability_execute_result filter runs after the ability’s registered execution callback and before output validation.

/**
 * Filters the result returned by an ability execute callback.
 *
 * @param mixed      $result       Result returned by the execute callback,
 *                                 or WP_Error when execution failed.
 * @param string     $ability_name Name of the ability.
 * @param mixed      $input        Normalized input.
 * @param WP_Ability $ability      Ability instance.
 */
apply_filters(
    'wp_ability_execute_result',
    $result,
    $ability_name,
    $input,
    $ability
);

Possible uses include:

  • Formatting a response.
  • Removing internal metadata.
  • Applying content-safety filtering.
  • Enriching a result.
  • Converting a successful result into an error.
  • Recovering from an execution error.

Removing internal response data

add_filter(
    'wp_ability_execute_result',
    function ( $result, $ability_name, $input, $ability ) {
        if (
            'my-plugin/get-report' !== $ability_name ||
            is_wp_error( $result ) ||
            ! is_array( $result )
        ) {
            return $result;
        }

        unset( $result['internal_debug_data'] );

        return $result;
    },
    10,
    4
);

The filtered result is subsequently validated against the ability’s output_schema.

Recovering from selected execution failures

The filter receives WP_Error values produced by the registered callback, so plugins may implement narrowly scoped recovery behaviour:

add_filter(
    'wp_ability_execute_result',
    function ( $result, $ability_name, $input, $ability ) {
        if (
            'my-plugin/get-remote-data' !== $ability_name ||
            ! is_wp_error( $result ) ||
            'remote_service_unavailable' !== $result->get_error_code()
        ) {
            return $result;
        }

        $fallback = my_plugin_get_fallback_data();

        /*
         * The fallback must conform to the ability's registered
         * output_schema because it will be validated after this filter.
         */
        return $fallback;
    },
    10,
    4
);

Any recovered value must still conform to the registered output_schema.

New WP_Filter_Sentinel class

WordPress 7.1 also introduces WP_Filter_Sentinel, a reusable marker class loaded alongside WP_Hook.

Core uses a unique sentinel instance as the default value for wp_pre_execute_ability. Comparing object identity allows Core to distinguish an unchanged default from every possible user-supplied value, including null, false, arrays and arbitrary objects.

Developers using wp_pre_execute_ability do not need to instantiate this class. To continue normal execution, callbacks should simply return the received $pre value unchanged.

Backward compatibility

These changes are additive:

  • Existing abilities require no changes.
  • Existing wp_before_execute_ability and wp_after_execute_ability callbacks continue to work.
  • Ability callbacks and permission callbacks retain their existing behaviour when none of the new filters is used.
  • Input and output schemas remain the final validation boundaries for normal execution.

Plugins that already provide their own ability-execution hooks may wish to evaluate whether some functionality can now use these Core filters. Protocol- or domain-specific hooks can still be layered on top when they need more specialised context.

Summary

WordPress 7.1 adds the following Abilities API filters:

Filter

Purpose

wp_pre_execute_ability

Short-circuit the complete execution pipeline

wp_ability_normalize_input

Transform normalized input before validation

wp_ability_permission_result

Modify or override permission results

wp_ability_execute_result

Transform or recover results before output validation

These filters make the Abilities API more extensible for AI integrations, automation systems, protocol adapters, authorisation layers and other tools that mediate ability execution.

For additional context, see Trac ticket #64989 and changeset #62397.

Props to @benjamin_zekavica and @audrasjb for peer review.

#7-1, #dev-notes, #dev-notes-7-1

Fetched July 29, 2026