Skip to content

REST API: Main-site-only sites endpoint, tighter response and a single user parameter - #12

Merged
lloc merged 3 commits into
lloc:feature/40365-rest-sites-endpointfrom
spacedmonkey:feature/40365-rest-sites-endpoint-feedback
Oct 8, 2026
Merged

lloc merged 3 commits into
lloc:feature/40365-rest-sites-endpointfrom
spacedmonkey:feature/40365-rest-sites-endpoint-feedback

Conversation

@spacedmonkey

Copy link
Copy Markdown

Addresses the feedback on WordPress#13055: the sites endpoint should work from the network's main site only, with the "my sites" use case kept narrow and read-only.

Main site only

  • Create, update, delete and single-site reads return a 403 from any site other than the main site: rest_cannot_create_not_on_main_site, rest_cannot_edit_not_on_main_site, rest_cannot_delete_not_on_main_site and rest_cannot_view_not_on_main_site.
  • Two exceptions work from any site, in the view context only:
    • listing your own sites (user=me or your own ID)
    • a member reading their own site
  • embed is no longer exempt for members.

Permissions

  • context=edit requires manage_sites on both the collection and single-site routes (rest_forbidden_context). This is checked before the site is looked up, so it doesn't reveal whether a site ID exists.
  • Listing another user's sites requires manage_sites (rest_forbidden_user).
  • Without manage_sites, filtering or ordering by fields outside the view context is rejected (rest_forbidden_param). Those fields are archived, mature, spam, deleted, lang_id, lang_id_exclude, before, after, and orderby=registered|last_updated.

Response and schema

  • These fields are now edit context only: registered, registered_gmt, last_updated, last_updated_gmt, archived, mature, spam, deleted, lang_id, post_count and the registered site meta, which may hold sensitive data.
  • New read-only admin_url field, built with get_admin_url().
  • home and siteurl now come from get_home_url() and get_site_url(), so the home_url and site_url filters apply.
  • The write-only title duplicated blogname, since wp_initialize_site() stores the title as blogname. It's gone: blogname is now writable when a site is created and ignored on update. Backslashes are kept.
  • A POST to /sites/{id} is an update, so it no longer runs the create-only path.

A single user parameter

  • The collection filter user and the create-only user_id are now one user parameter. It is validated to accept a positive user ID or "me", and get_user_id_from_param() resolves "me" to the current user.
  • Invalid values (xyz, 0, -1, an empty string, a list) are rejected with rest_invalid_param.
  • An unknown administrator on create returns rest_user_invalid_id.

Other changes

  • When the user filter matches no sites, get_items skips both site queries and still fires rest_site_query, so a plugin can widen the result.

Testing

  • npm run test:php -- -c tests/phpunit/multisite.xml --filter 'WP_Test_REST_Sites_Controller|WP_Test_REST_Site_Meta_Fields': 254 tests pass.
  • The single-site sites tests pass. The multisite restapi group only fails Test_oEmbed_Controller::test_proxy_with_classic_embed_provider, which also fails without these changes.
  • composer lint and composer phpstan are clean.
  • tests/qunit/fixtures/wp-api-generated.js is regenerated for the changed args.

🤖 Generated with Claude Code

spacedmonkey and others added 3 commits October 7, 2026 21:38
…esponse.

Following the review on PR 13055, the sites endpoint now behaves like the
network admin and is only usable from the network's main site:

- Create, update, delete and single-site reads return a 403
  `rest_cannot_{create,edit,delete,view}_not_on_main_site` error from any
  other site.
- Listing your own sites (`user=me` or your own ID) and a member reading
  their site stay available from any site, but only in the view context.
- Users listing their own sites cannot filter or order by fields outside
  the view context (`rest_forbidden_param`), so hidden values cannot be
  inferred.
- The status flags, dates, `lang_id`, `post_count` and the site meta are
  now edit-context only.
- Add a read-only `admin_url` field and build `home` and `siteurl` with
  get_home_url() and get_site_url(), so their filters apply.
- Replace the write-only `title` property with a create-only, writable
  `blogname`, and move `user_id` out of the schema into the create route
  arguments. A POST to an existing site no longer runs the create path.
- Skip the site queries when the user filter matches no sites, while
  still firing `rest_site_query`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The collection filter `user` and the create-only `user_id` argument
described the same relationship under two names. Both are now `user`,
sharing one schema that accepts a positive user ID or "me", and
get_user_id_from_param() resolves "me" to the current user.

- Invalid values such as `xyz`, `0`, `-1` or an empty string are now
  rejected with `rest_invalid_param` before the permission check.
- Numeric strings are sanitized to integers.
- An unknown administrator on creation returns `rest_user_invalid_id`.
- Listing the sites of another user requires the capability to manage
  sites (`rest_forbidden_user`).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Both read permission checks reject `context=edit` up front with
  `rest_forbidden_context` unless the user can manage sites, before the
  member, main-site and site lookup checks, so an edit-context request
  does not reveal whether a site exists.
- get_items_permissions_check() resolves the own-user filter once and
  reuses it for the other-user check, so a logged-out `user=me` now
  returns `rest_forbidden_user`.
- The check against filtering or ordering by fields outside the view
  context now applies to every user without the capability, and still
  runs before the own-sites exemption.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Oct 7, 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.

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

Props spacedmonkey.

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

@lloc
lloc merged commit 06c9142 into lloc:feature/40365-rest-sites-endpoint Oct 8, 2026
3 checks passed
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.

2 participants