[wp-trac] [WordPress Trac] #64926: REST API: GET requests fail object/array schema validation when params are JSON-serialized strings

WordPress Trac noreply at wordpress.org
Tue Apr 7 12:31:31 UTC 2026


#64926: REST API: GET requests fail object/array schema validation when params are
JSON-serialized strings
-------------------------+-------------------------------------------------
 Reporter:  dsmy         |       Owner:  (none)
     Type:  defect       |      Status:  new
  (bug)                  |
 Priority:  normal       |   Milestone:  Awaiting Review
Component:  REST API     |     Version:
 Severity:  normal       |  Resolution:
 Keywords:  has-patch    |     Focuses:  javascript, rest-api, php-
                         |  compatibility
-------------------------+-------------------------------------------------

Comment (by westonruter):

 Something that just came to mind: the abilities in the Abilities API can
 be exposed in the REST API. When an ability is read-only, then a `GET`
 request is made. Nevertheless, the `input_schema` may involve an object of
 properties.

 Consider this Abilities API code:

 {{{#!php
 <?php
 add_action(
         'wp_abilities_api_init',
         static function () {
                 wp_register_ability(
                         'abilities-experiment/get-post',
                         array(
                                 'label'               => __( 'Get Post',
 'abilities-experiment' ),
                                 'description'         => __( 'Gets fields
 for a post', 'abilities-experiment' ),
                                 'category'            => 'site',
                                 'input_schema'        => array(
                                         'type'                 =>
 'object',
                                         'required'             => array(
 'post' ),
                                         'properties'           => array(
                                                 'post' => array(
                                                         'type' =>
 'object',
                                                         'required' =>
 array( 'id' ),
                                                         'properties' =>
 array(
                                                                 'id' =>
 array(
 'type'        => 'integer',
 'description' => __( 'The ID of the post to get.', 'abilities-experiment'
 ),
 'minimum'     => 1,
                                                                 ),
                                                         )
                                                 ),
                                                 'fields' => array(
                                                         'type' => 'array',
                                                         'items' => array(
                                                                 'type' =>
 'string',
                                                         ),
                                                 )
                                         ),
                                 ),
                                 'output_schema'       => array(
                                         'type'                 =>
 'object',
                                 ),
                                 'execute_callback'    => static function (
 array $input ): array|WP_Error {
                                         $post = get_post(
 $input['post']['id'] );
                                         if ( ! $post ) {
                                                 return new WP_Error(
 'post_not_found', __( 'Post not found.', 'abilities-experiment' ), array(
 'status' => 404 ) );
                                         }
                                         $data = $post->to_array();
                                         if ( $input['fields'] ) {
                                                 $data =
 wp_array_slice_assoc( $data, $input['fields'] );
                                         }
                                         return $data;
                                 },
                                 'permission_callback' => static function (
 array $input ): bool {
                                         return current_user_can(
 'edit_posts', $input['post']['id'] );
                                 },
                                 'meta'                => array(
                                         'annotations'  => array(
                                                 'readonly'    => true,
                                                 'destructive' => false,
                                                 'idempotent'  => false,
                                         ),
                                         'show_in_rest' => true,
                                 ),
                         )
                 );
         }
 );
 }}}

 Using the Abilities API client module as follows:

 {{{#!js
 (await import("@wordpress/abilities")).executeAbility(
   "abilities-experiment/get-post",
   { post: { id: 1 }, fields: ["post_content", "post_author"] },
 );
 }}}

 This results in an HTTP request being made to the REST API as follows:

 `/wp-json/wp-abilities/v1/abilities/abilities-experiment/get-
 post/run?input%5Bpost%5D%5Bid%5D=1&input%5Bfields%5D%5B0%5D=post_content&input%5Bfields%5D%5B1%5D=post_author&_locale=user`

 With the brackets decoded:

 `/wp-json/wp-abilities/v1/abilities/abilities-experiment/get-
 post/run?input[post][id]=1&input[fields][0]=post_content&input[fields][1]=post_author&_locale=user`

 So, note that `@wordpress/abilities` (er, `@wordpress/core-abilities`) is
 already handling this correctly, converting a nested object into a list of
 URL query params.

 See
 [https://github.com/WordPress/gutenberg/blob/6ea168835304764d253d169906148cf5fe6269d9/packages
 /core-abilities/src/index.ts#L27-L71 logic] in `createServerCallback()`:

 {{{
 // For GET and DELETE requests, pass the input as query parameters.
 path = addQueryArgs( path, { input } );
 }}}

 This `addQueryArgs()` function is part of the `@wordpress/url` package:
 https://developer.wordpress.org/block-editor/reference-guides/packages
 /packages-url/#addqueryargs

 So you can use that instead of the `qs` package.

-- 
Ticket URL: <https://core.trac.wordpress.org/ticket/64926#comment:25>
WordPress Trac <https://core.trac.wordpress.org/>
WordPress publishing platform


More information about the wp-trac mailing list