Skip to content

REST API endpoints for sites - #13055

Open
lloc wants to merge 49 commits into
WordPress:trunkfrom
lloc:feature/40365-rest-sites-endpoint
Open

lloc wants to merge 49 commits into
WordPress:trunkfrom
lloc:feature/40365-rest-sites-endpoint

Conversation

@lloc

@lloc lloc commented Aug 14, 2026

Copy link
Copy Markdown

Trac ticket: https://core.trac.wordpress.org/ticket/40365

There are some open questions that we should discuss in the trac ticket.

@github-actions

github-actions Bot commented Aug 14, 2026 •

Copy link
Copy Markdown

The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the props-bot label.

Unlinked Accounts

The following contributors have not linked their GitHub and WordPress.org accounts: @jonnydmg.

Contributors, please read how to link your accounts to ensure your work is properly credited in WordPress releases.

Core Committers: Use this line as a base for the props when committing in SVN:

Props realloc, spacedmonkey, peterwilsoncc, biont.

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

@github-actions

Copy link
Copy Markdown

Test using WordPress Playground

The changes in this pull request can previewed and tested using a WordPress Playground instance.

WordPress Playground is an experimental project that creates a full WordPress instance entirely within the browser.

Some things to be aware of

  • All changes will be lost when closing a tab with a Playground instance.
  • All changes will be lost when refreshing the page.
  • A fresh instance is created each time the link below is clicked.
  • Every time this pull request is updated, a new ZIP file containing all changes is created. If changes are not reflected in the Playground instance,
    it's possible that the most recent build failed, or has not completed. Check the list of workflow runs to be sure.

For more details about these limitations and more, check out the Limitations page in the WordPress Playground documentation.

Test this pull request with WordPress Playground.

@jonnydmg jonnydmg left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a big PR to review. Good work getting this ready for core. This code a little old and some things have changed in core, so this PR needs to update accordingly.

Biggest things.

  • We need to support sites with is_site_meta_supported false. There was some work done there to support it but not complete.
  • Site endpoint needs to be registered on single site. So it always exists then return an error on single site.
  • WP_REST_Site_Meta_Fields has no test coverage at all.
  • Single site needs tests.
  • Super admin should be allowed to do everything on the endpoint.
  • Code coverage and ticket need needs to be added to every test.

This feel like we are on the right track, if I am nitpicing here, becuase I think this good to go into core and just want to get the final bits to push forward into core.

I will wait until feedback is complete to do some real testing for this PR.

Comment thread src/wp-includes/rest-api/endpoints/class-wp-rest-sites-controller.php Outdated
Comment thread tests/phpunit/tests/rest-api/rest-sites-controller.php Outdated
Comment thread src/wp-includes/rest-api.php Outdated
Comment thread src/wp-includes/rest-api/endpoints/class-wp-rest-sites-controller.php Outdated
Comment thread src/wp-includes/rest-api/endpoints/class-wp-rest-sites-controller.php Outdated
Comment thread src/wp-includes/rest-api/endpoints/class-wp-rest-sites-controller.php Outdated

@jonnydmg jonnydmg left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a big PR to review. Good work getting this ready for core. This code a little old and some things have changed in core, so this PR needs to update accordingly.

Biggest things.

  • We need to support sites with is_site_meta_supported false. There was some work done there to support it but not complete.
  • Site endpoint needs to be registered on single site. So it always exists then return an error on single site.
  • WP_REST_Site_Meta_Fields has no test coverage at all.
  • Single site needs tests.
  • Super admin should be allowed to do everything on the endpoint.
  • Code coverage and ticket need needs to be added to every test.

This feel like we are on the right track, if I am nitpicing here, becuase I think this good to go into core and just want to get the final bits to push forward into core.

I will wait until feedback is complete to do some real testing for this PR.

Introduces WP_REST_Sites_Controller plus tests.
See #40365.
@lloc
lloc force-pushed the feature/40365-rest-sites-endpoint branch from aa03545 to cc81250 Compare August 22, 2026 07:51
@spacedmonkey

Copy link
Copy Markdown
Member

@lloc I have put together a little PR for tests for all the meta changes -lloc#1

@spacedmonkey spacedmonkey left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here is a PR with some unit test improvements.

lloc#2

This is has unit tests that cover single site as well.

Comment thread tests/phpunit/tests/rest-api/wpRestSitesController.php
lloc and others added 7 commits September 2, 2026 15:48
…oint-tests

Tests: Add unit tests for WP_REST_Site_Meta_Fields functionality
…oint-more-tests

Tests: Rename rest-sites-controller.php to wpRestSitesController.php …
Add format validation for the `domain` and `path` parameters in
WP_REST_Sites_Controller so malformed values are rejected with a clean
400 rest_invalid_param instead of failing later inside wp_insert_site()/
wp_update_site() (which surfaces as a 500).

domain must be a valid hostname or bare IPv4/IPv6 address, optionally
followed by a port (bracketed IPv6 is intentionally not supported, so
IPv6 addresses can't carry a port). path must start and end with a
forward slash and be made up of characters valid in a URL path segment.

Ticket #40365.
…t-check-exists' into feature/40365-rest-sites-endpoint-check-exists

# Conflicts:
#	src/wp-includes/rest-api/endpoints/class-wp-rest-sites-controller.php
@spacedmonkey

Copy link
Copy Markdown
Member

I have more feedback here.

lloc#4
lloc#3

This fixes some issues that were flagged by ai.

…oint-force-delete

Enhance site deletion process to support force deletion and user removal
…oint-check-exists

Check if domain exists before creating / updating.
Comment thread tests/qunit/fixtures/wp-api-generated.js Outdated
Comment thread tests/phpunit/tests/rest-api/wpRestSiteMetaFields.php Outdated
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@spacedmonkey

Copy link
Copy Markdown
Member

Opened a follow-up PR against feature/40365-rest-sites-endpoint: lloc#8

What it fixes: the wp/v2/sites REST endpoint currently scopes access checks (create_sites, manage_sites, and site membership) to the current network only, via is_super_admin()/get_super_admins() with no network context. That means a super admin of network B has no way to manage sites on network B through the REST API unless they're also a super admin of whichever network the request happens to be authenticated against.

This PR adds an optional $network_id parameter to is_super_admin() and get_super_admins(), so super-admin status can be checked against a specific network (falling back to each network's own site_admins option instead of always defaulting to the current network). The sites controller's permission checks (check_network_ids_exist() / check_network_access()) then use this to validate and authorize network/network_exclude request parameters on the list, single-item, create, and update routes, so a network-scoped super admin can be granted (or correctly denied) access to sites on a specific network rather than only the current one.

New PHPUnit coverage is included for the network-id validation and cross-network access behavior on those routes. Both the single-site and multisite WP_Test_REST_Sites_Controller suites pass.

🤖 Generated with Claude Code

…oint-super-admin

Add per-network super-admin scoping to REST sites controller
…oint-v2

Define allow_batch support for site endpoints
REST API: Rename check_domain_is_available() to check_url_is_available()
…sites-permissions

Combine the per-network access checks from #7 with the refactored
permission checks from this branch, and align the #6/#7 tests with the
tightened permissions.
@lloc

lloc commented Sep 30, 2026 •

Copy link
Copy Markdown
Author

Quick question: Does this entirely supersede #9626? Let me know if there are any gaps or if we can simply close the other one.

Hey @Biont, yes, I think we can close #9626. It adds a read-only GET /sites list, and this PR covers that plus full CRUD, site meta, and tests. If you had a use case in mind that's still missing here, let us know.

@Biont

Biont commented Sep 30, 2026

Copy link
Copy Markdown

Hey @Biont, yes, I think we can close #9626. It adds a read-only GET /sites list, and this PR covers that plus full CRUD, site meta, and tests.

Love it. Thanks so much

Comment thread tests/qunit/fixtures/wp-api-generated.js Outdated
spacedmonkey and others added 2 commits September 30, 2026 22:28
Combines check_network_ids_exist() and check_network_access() into one
check_network_ids() method, used consistently across get_items, get_item,
create_item, update_item, and delete_item_permissions_check(). Also adds
test coverage for delete_item_permissions_check() network validation,
which previously had none.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@spacedmonkey

Copy link
Copy Markdown
Member

While reviewing WP_REST_Sites_Controller, I noticed delete_item_permissions_check() performed no network-ID validation at all — unlike get_items, get_item, create_item, and update_item_permissions_check(), it never called check_network_ids_exist() / check_network_access(). That means a request to delete a site on a network the requester isn't a super admin of (or an ID that doesn't correspond to a real network) would skip straight to the delete-capability check, rather than being rejected for lacking network access.

I've opened a fix here: lloc#9

It:

  • Adds the missing network-ID check to delete_item_permissions_check(), consistent with the other four permission-check methods.
  • Also fixes the check ordering in get_items_permissions_check() so network validation runs before the edit/own-user permission checks.
  • Merges check_network_ids_exist() and check_network_access() into a single check_network_ids() method to remove the duplicated two-step pattern that was repeated at all 5 call sites.
  • Adds PHPUnit coverage for the new delete-permission network checks.

npm run test:php -- --filter WP_Test_REST_Sites_Controller -c tests/phpunit/multisite.xml passes (154 tests, 555 assertions).

…oint-fix

REST Sites: Fix network permission checks and add delete permission coverage
Comment thread src/wp-includes/rest-api/endpoints/class-wp-rest-sites-controller.php Outdated
Comment thread tests/qunit/fixtures/wp-api-generated.js Outdated

@spacedmonkey spacedmonkey left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have spent a long time testing this and I am happy to say, I am happy to commit. Thanks for all the amazing work @lloc, thanks for getting this one across the line.

@peterwilsoncc peterwilsoncc left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While the changes to checking and getting super admins may be helpful for a programmatic API I don't think that querying one network from another via a public API is a security risk.

For example, this current implimentation exposes cross nmetwork sites when no network is specified.

  1. Install and network enable WP Multi Network
  2. Create a new network
  3. Create a new user (n2admin)
  4. Set n2admin as a super user of the second network
  5. Log in to the second network as n2admin
  6. Observe in the dashboard "My Networks" they only have access to network 2
  7. In the browser console run the command wp.apiFetch( { 'path': '/wp/v2/sites' } )
  8. Observe the result contains details for both the network they are super admin of and the network

This is an example of why I made my earlier comment that the network should be inferred from the URL on which the request is being made. It's a security risk but, equally significantly, details of one network should not be made available from another. Doing so breaks the isolation multiple networks are intended to maintain.

@jonnydmg

jonnydmg commented Oct 2, 2026 •

Copy link
Copy Markdown

While the changes to checking and getting super admins may be helpful for a programmatic API I don't think that querying one network from another via a public API is a security risk.

For example, this current implimentation exposes cross nmetwork sites when no network is specified.

  1. Install and network enable WP Multi Network
  2. Create a new network
  3. Create a new user (n2admin)
  4. Set n2admin as a super user of the second network
  5. Log in to the second network as n2admin
  6. Observe in the dashboard "My Networks" they only have access to network 2
  7. In the browser console run the command wp.apiFetch( { 'path': '/wp/v2/sites' } )
  8. Observe the result contains details for both the network they are super admin of and the network

This is an example of why I made my earlier comment that the network should be inferred from the URL on which the request is being made. It's a security risk but, equally significantly, details of one network should not be made available from another. Doing so breaks the isolation multiple networks are intended to maintain.

That is a bug and sorry that one is me. This commit 959bf3d.

I will fix it.

Fixed in
lloc#10

@peterwilsoncc

Copy link
Copy Markdown
Contributor

@spacedmonkey @jonnydmg The endpoint just can't allow querying across network, it will introduce data exposure no matter how it's coded up

Each network is entirely independent: logins, network activated plugins, mu-plugins can be network specific via get_current_network_id(). Each site can have a different set of plugins activated too.

One network may hard code super admins via pre_site_option_{$option}, another network may not. Access on the network that does so will bypass the filter on the network that does not.

The same will occur if one network filters the manage_sites permission via the map_meta_cap filter, the other may not.

Even for sites on the same network, the plugins can differ site-to-site so the endpoint can't be available on sub-sites due to different plugins.

This is what I think needs to happen before this can be considered secure enough to commit:

  1. No cross-network queries, the network_id parameter is hard coded to the current network in WP_Site_Query()
  2. The endpoint is only available on the main site, ie is_main_site() === true -- this matches the network admin behaviour in which /sub-site/wp-admin/network redirects to /wp-admin/network on the main site.

This will ensure that plugins and mu-plugins for the endpoint match what is loaded in the network admin currently.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants