Skip to content

Refactor and update the datapoint route registration to make use of WordPress' REST schema capabilities #6759

Closed

Description

@techanvil

Feature Description

As mentioned here, it would be nice to be able to leverage WordPress' argument schema validation for module datapoint REST requests. Additionally, we could make use of the resource schema capabilities too.

This would require some refactoring in order to register the datapoint routes individually, rather than using a dynamic path parameter.


Do not alter or remove anything below. The following sections will be managed by moderators only.

Acceptance criteria

  • A module data endpoint can be registered as its own REST route, with its own argument schema describing the parameters it accepts and its own resource schema describing the data it returns.
  • Module data endpoints that do not declare a schema keep being served by the existing catch-all route, with unchanged behaviour.
  • An endpoint that declares a schema returns the same data, status codes and error messages as it does today, apart from the new validation errors.
  • A request to an endpoint that declares a schema is rejected with a 400 response when a required parameter is missing or a parameter has the wrong type, before the request reaches Google's APIs.
  • The argument and resource schemas of endpoints that declare them appear in the site's REST API index.
  • No existing module data endpoint is converted in this issue: it only adds the ability to do so, and the conversions are handled separately.
  • The Site Kit dashboard, module setup, module settings and shared dashboards continue to work unchanged.

Implementation Brief

  • In includes/Core/Modules/Schema_Aware_Datapoint.php (new file):
    • Declare an interface Schema_Aware_Datapoint, following Executable_Datapoint, with get_args() returning the endpoint argument schema and get_schema() returning the resource schema.
  • In includes/Core/Modules/Module.php:
    • Move the lazy memoization of $datapoint_definitions out of get_datapoint_definition() into a private helper.
    • Add final public function get_schema_aware_datapoints() returning the memoized definitions whose values implement Schema_Aware_Datapoint, keyed by their METHOD:datapoint id.
  • In includes/Core/Modules/REST_Modules_Controller.php:
    • Move the WP_REST_Server::READABLE and WP_REST_Server::EDITABLE callbacks in get_modules_data_route() into private methods taking the module slug and datapoint name, so the catch-all route and the per-datapoint routes run the same code, including the invalid_module_slug and module_not_active errors.
    • Do the same for resolve_datapoint_permission(), so a datapoint implementing Permission_Aware_Datapoint keeps its own permission check on either route.
    • Add get_datapoint_routes(): for each module returned by Modules::get_available_modules(), call get_schema_aware_datapoints() and build one REST_Route per datapoint name at modules/<slug>/data/<datapoint>, with the GET: and POST: definitions of the same name becoming the two endpoints of that route.
    • Pass each definition's get_args() as the endpoint args, and get_schema() wrapped in a closure as the route-level schema key, matching get_modules_list_route().
    • In get_rest_routes(), return the get_datapoint_routes() entries before get_modules_data_route(). WP_REST_Server matches routes in registration order, so the catch-all has to stay last.
    • A datapoint name needs no special handling when only one of its GET: and POST: definitions declares a schema: WP_REST_Server moves on to the next matching route when the individual route has no handler for the request method, so the other method falls through to the catch-all.
  • Datapoints keep their existing Missing_Required_Param_Exception guards. Module::get_data() is also called directly from PHP, for example from Analytics_4::get_google_tag_settings(), where REST validation does not run.

Test Coverage

  • Add tests/phpunit/includes/Core/Modules/Datapoints/FakeModule_Schema_Aware_Request.php, a fake datapoint implementing Schema_Aware_Datapoint with a required string argument and a resource schema, registered on FakeModule alongside FakeModule_Test_Request.
  • In tests/phpunit/integration/Core/Modules/ModuleTest.php:
    • get_schema_aware_datapoints() returns only the definitions implementing the interface, keyed by METHOD:datapoint.
  • In tests/phpunit/integration/Core/Modules/REST_Modules_ControllerTest.php:
    • the schema-aware fake datapoint is served by its own route and returns the same data as before.
    • a request to that route without the required parameter returns a 400 and the datapoint is never executed.
    • a fake datapoint that does not implement the interface is still served by the catch-all route.
    • the individual route wins over the catch-all when both would match the path.
    • requesting the schema-aware datapoint on a deactivated module returns module_not_active.
    • a schema-aware datapoint that also implements Permission_Aware_Datapoint uses its own permission check on the individual route.
    • the route, its argument schema and its resource schema are listed in the google-site-kit/v1 REST index.

QA Brief

This PR lets a module datapoint declare the parameters it accepts, so Site Kit can check them before the datapoint runs. No datapoint declares them yet; that starts with #6760.

Setup

  • Site Kit set up and connected with Analytics and Search Console. AdSense not set up, so it is not active.
  • As an administrator, open "Dashboard sharing" from the Site Kit dashboard header and share Analytics and Search Console with Editors.
  • The console steps below use this helper. Paste it into the DevTools console on the Site Kit dashboard first:
const siteKitRequest = ( method, path, body ) => fetch( _googlesitekitAPIFetchData.rootURL + 'google-site-kit/v1/' + path, { method, headers: { 'X-WP-Nonce': _googlesitekitAPIFetchData.nonce, 'Content-Type': 'application/json' }, body: body && JSON.stringify( body ) } ).then( async ( response ) => console.log( method, response.status, await response.text() ) );

Module data still loads

  • As an administrator, go to Site Kit > Dashboard. Open DevTools > Network and filter by google-site-kit/v1/modules/. Scroll slowly to the bottom of the page, since widgets only request their data once they are on screen.
  • Expected: the widgets show data as before, and every modules/<module>/data/<datapoint> request returns 200.
  • Log in as an editor and repeat step 1.
  • Expected: the shared widgets show data as before, and every modules/<module>/data/<datapoint> request returns 200.
    PUT and PATCH use the same permission check as POST
  • As an editor, on the Site Kit dashboard, run:
siteKitRequest( 'POST', 'modules/analytics-4/data/save-site-goals-settings', { data: { settings: {} } } );
siteKitRequest( 'PUT', 'modules/analytics-4/data/save-site-goals-settings', { data: { settings: {} } } );
siteKitRequest( 'PATCH', 'modules/analytics-4/data/save-site-goals-settings', { data: { settings: {} } } );

The empty settings object saves nothing, so this doesn't change the site.

  • Expected: all three log status 200. (Before this PR, POST returned 200 but PUT and PATCH returned 403 rest_forbidden.)

Errors are unchanged

  • As an administrator, on the Site Kit dashboard, run:
siteKitRequest( 'GET', 'modules/analytics-4/data/not-a-datapoint' );
siteKitRequest( 'POST', 'modules/analytics-4/data/not-a-datapoint' );
siteKitRequest( 'GET', 'modules/adsense/data/accounts' );
  • Expected: the first two log GET 400 and POST 400 with "code":"invalid_datapoint".

Notes

Changelog entry

  • Add support for schemas in module datapoint routes.

Activity

  1. added
    P2Low priority
    and removed
    P2Low priority
    Type: EnhancementImprovement of an existing feature
    on Dec 11, 2023
  2. changed the title [-]Refactor and update the datapoint route registration to make use of WordPress' REST schema capabilities.[/-] [+]Refactor and update the datapoint route registration to make use of WordPress' REST schema capabilities[/+] on Dec 11, 2023
  3. self-assigned this
    on Jul 14, 2025
  4. removed their assignment
    on Jul 30, 2025
  5. self-assigned this
    on Aug 5, 2025
  6. techanvil commented on Aug 5, 2025

    @techanvil
    CollaboratorAuthor

    Hey @benbowler, thanks for drafting these AC. However, the focus of them is not aligned with the scope of the issue.

    It's also worth noting that the REST_Routes infrastructure does already support argument and resource schemas, so I wouldn't say the REST_Routes infra itself needs an update.

    The focus of this issue is in fact the module datapoint routes, rather than all routes. As the feature description points out, the datapoint routes are dynamic rather than registered individually, so we need to refactor them to allow individual registration with argument and resource schemas.

    new REST_Route(
    'modules/(?P<slug>[a-z0-9\-]+)/data/(?P<datapoint>[a-z\-]+)',

    This itself is a significant enough piece of work that it will need to be broken down into multiple issues, I imagine the first issue (i.e., this one) will be to refactor the datapoint route registration in a way that allows for individual routes to be registered incrementally while retaining the catchall for the rest of the routes.

    It would of course be good to ensure that all of our REST routes provide argument and resource schemas, but that's something we can address separately. I've created a new issue for this: #11234

  7. techanvil commented on Aug 6, 2025

    @techanvil
    CollaboratorAuthor

    I've created a new issue for this: #11234

    ... and then realised it's a duplicate of #6760, and closed it. 🤦

  8. 4 remaining items

  9. assigned and unassigned on Oct 5, 2026
  10. removed their assignment
    on Oct 7, 2026
  11. self-assigned this
    on Oct 8, 2026
  12. kelvinballoo commented on Oct 9, 2026

    @kelvinballoo
    Collaborator

    QA Update ✅

    Verified and outcomes are as specified in the QAB. Moving to approval

    • Main admin

      Details All are with status 200.
      6759.-.01.-.Main.admin.-.200.status.ok.mov

      Errors are unchanged, as expected.

      Image
    • View only

      Details
      6759.-.02.-.editor.-.200.status.ok.mov
      6759.-.02.-.editor.-.POST.PATCH.PUT.200.ok.mov
  13. removed their assignment
    on Oct 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Low priorityPHPTeam SIssues for Squad 1Type: EnhancementImprovement of an existing feature

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions