Skip to content

REST API: Explore a dedicated notes endpoint - #13043

Draft
adamsilverstein wants to merge 4 commits into
WordPress:trunkfrom
adamsilverstein:add/rest-notes-controller
Draft

REST API: Explore a dedicated notes endpoint#13043
adamsilverstein wants to merge 4 commits into
WordPress:trunkfrom
adamsilverstein:add/rest-notes-controller

Conversation

@adamsilverstein

@adamsilverstein adamsilverstein commented Aug 13, 2026

Copy link
Copy Markdown
Member

What

Exploratory Core companion to Gutenberg #81599, which answers Mamaduka's suggestion that notes deserve their own REST endpoint instead of a filtered view of wp/v2/comments.

Two halves, one per commit, so the trade is easy to read:

  1. Add WP_REST_Notes_Controller, serving wp/v2/notes.
  2. Take note handling back out of WP_REST_Comments_Controller.

The second half is the interesting one. wp/v2/comments currently carries note rules in nine places. After this, it carries one.

The endpoint

WP_REST_Notes_Controller extends WP_REST_Comments_Controller, so notes stay comment rows: the same comment meta, the same rest_prepare_comment filter, and anything registered through register_rest_field( 'comment', ... ) all keep applying. The schema title stays comment on purpose.

What changes is the shape of the collection.

wp/v2/comments?type=note wp/v2/notes
Scoping client sends type=note&status=all every time fixed by the route
post optional required, and access is one question: can you edit_post?
Replies sibling rows the client re-parents nested in a replies array on their thread
Reply context _embed prepares children in view, so no content.raw prepared in the thread's context, content.raw all the way down
Pagination a page break can separate a reply from its parent pages cut between threads; X-WP-Total counts threads
Tally fetch every row to count _fields=id,post,reply_count
Per-note cost a COUNT query for the children link no children link, replies already travel inside

Replies for a whole page are fetched in one WP_Comment_Query, so the cost does not grow with the number of threads on the page.

What comes out of the comments controller

WP_REST_Comments_Controller: +54 / -129. Its tests: -440.

Removed:

  • the $is_note branch and the forbidden-params fallback in get_items_permissions_check()
  • the edit_post re-mapping of edit context, in both get_items_permissions_check() and get_item_permissions_check()
  • the note login requirement, rest_cannot_create_note, the edit_post status cap, and the post type support check in create_item_permissions_check()
  • the && ! $is_note carve-outs that let notes past the draft and comments_open gates
  • 'note' in the create-route type allowlist
  • the wp_allow_comment() bypass for notes
  • the _wp_note_status injection before the content check, and the empty-note allowance inside it
  • the children link rebuild that re-added type and status for notes
  • check_post_type_supports_notes() itself

Three protected seams take their place, each with a one-line default that restores the pre-notes behaviour:

protected function get_allowed_comment_types()                                  // array( 'comment' )
protected function prepare_comment_for_content_check( $prepared, $request )      // $prepared
protected function determine_comment_approval( $prepared )                       // wp_allow_comment( $prepared, true )

The one branch that has to stay

check_read_permission() keeps excluding notes from the "approved comment on a readable post" shortcut:

if ( 'note' !== $comment->comment_type && ! empty( $comment->comment_post_ID ) ) {

Every note is stored approved - approved means resolved, not public - so without this line an anonymous request to wp/v2/comments/<id> returns any note on a public post. A new endpoint does not remove notes from the comments routes' ID space, so the guard belongs where the leak is. Removing it turned test_get_items_type_arg_unauthenticated red, which is a good test.

Testing

tests/phpunit/tests/rest-api/rest-notes-controller.php, 36 tests / 127 assertions, covering the nine behaviours that moved plus threading, context, pagination, _fields, and the role matrix that data_note_get_items_permissions_data_provider used to cover.

tests/phpunit/tests/comment/wpNotifyNoteMentions.php has two tests that post and update a note over HTTP to exercise the rest_insert_comment wiring. They follow notes to wp/v2/notes.

npm run test:php -- --filter 'WP_Test_REST_Notes_Controller|WP_Test_REST_Comments_Controller'
OK (210 tests, 1045 assertions)

npm run test:php
Tests: 30864, Assertions: 4558985, Failures: 2

Both remaining failures are local environment, not this branch: Test_oEmbed_Controller::test_proxy_with_classic_embed_provider needs outbound HTTP, and Tests_Script_Modules_WpScriptModules::test_default_script_module_files_exist wants built files this worktree has not generated.

tests/qunit/fixtures/wp-api-generated.js is regenerated for the two new routes, in its own commit.

Open questions

  • Trac ticket. Not filed yet. The notes REST work so far sits under #64096 and #64152; this may want its own.
  • Deprecation path. wp/v2/comments?type=note works today and Gutenberg trunk still uses it. This branch removes the permission handling that makes it usable, which is a decision to make deliberately, not a side effect. Keeping both alive through one release is the conservative option.
  • edit_comment for the single-note route. get_item_permissions_check() here uses edit_comment, which maps to edit_post on the parent. That preserves what the comments controller did, but is worth a second look.
  • Preloading. block_editor_rest_api_preload_paths would want wp/v2/notes alongside or instead of the comments path.

If this direction holds, the Gutenberg side needs no compat shim at all - the plugin can drop its copy of the controller and consume the Core route directly.

AI Use

Claude Code did the typing here, I did the asking. I will review and test.

Serve editorial notes from wp/v2/notes instead of asking clients to filter
wp/v2/comments by type. The collection returns threads with their replies
nested and prepared in the same context, so pagination never orphans a reply
and context=edit carries content.raw all the way down, which _embed cannot do.

Opens up check_post_type_supports_notes() so the subclass can reuse it.
With wp/v2/notes serving notes, the comments controller no longer needs to
branch on comment type. Note permissions, the empty-note-on-resolve
allowance, duplicate and flood bypass, and the post type support check all
move to WP_REST_Notes_Controller, reached through three protected seams:
get_allowed_comment_types(), prepare_comment_for_content_check() and
determine_comment_approval().

One branch stays: check_read_permission() keeps excluding notes from the
approved-comment shortcut. Notes are stored approved, so without it the
comments routes would hand them to anonymous readers by ID.
@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.

These two exercise the rest_insert_comment wiring through HTTP, so they have
to follow notes to wp/v2/notes now that the comments route no longer accepts
the note type.
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.

1 participant