REST API: Explore a dedicated notes endpoint - #13043
Conversation
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.
Test using WordPress PlaygroundThe 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
For more details about these limitations and more, check out the Limitations page in the WordPress Playground documentation. |
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.
Resolving or reopening a thread writes a reply of its own carrying `_wp_note_status`, so a thread nobody answered reported two replies once it had been resolved and reopened. Count only the replies someone wrote, while still returning the resolution entries in `replies` so the thread can render its history.
|
Claude carried a fix over from the plugin PR, details below: @Mamaduka's review on WordPress/gutenberg#81599 asked whether the notes count should include replies, and whether replies to resolutions count. Chasing that turned up a bug in Resolving or reopening a thread does not just flip Test is The other half of that review, moving the client entity to a |
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:
WP_REST_Notes_Controller, servingwp/v2/notes.WP_REST_Comments_Controller.The second half is the interesting one.
wp/v2/commentscurrently 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 samerest_prepare_commentfilter, and anything registered throughregister_rest_field( 'comment', ... )all keep applying. The schema title stayscommenton purpose.What changes is the shape of the collection.
wp/v2/comments?type=notewp/v2/notestype=note&status=allevery timepostedit_post?repliesarray on their thread_embedprepares children inview, so nocontent.rawcontent.rawall the way downX-WP-Totalcounts threads_fields=id,post,reply_countCOUNTquery for thechildrenlinkchildrenlink, replies already travel insideReplies 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.reply_countcounts the replies someone wrote. Resolving or reopening a thread records a reply of its own carrying_wp_note_status, and those would otherwise read as replies - a thread nobody answered reported two of them once it had been resolved and reopened. They stay inreplies, since the thread renders them as status entries.What comes out of the comments controller
WP_REST_Comments_Controller: +54 / -129. Its tests: -440.Removed:
$is_notebranch and the forbidden-params fallback inget_items_permissions_check()edit_postre-mapping of edit context, in bothget_items_permissions_check()andget_item_permissions_check()rest_cannot_create_note, theedit_poststatus cap, and the post type support check increate_item_permissions_check()&& ! $is_notecarve-outs that let notes past the draft andcomments_opengates'note'in the create-route type allowlistwp_allow_comment()bypass for notes_wp_note_statusinjection before the content check, and the empty-note allowance inside itchildrenlink rebuild that re-addedtypeandstatusfor notescheck_post_type_supports_notes()itselfThree protected seams take their place, each with a one-line default that restores the pre-notes behaviour:
The one branch that has to stay
check_read_permission()keeps excluding notes from the "approved comment on a readable post" shortcut:Every note is stored approved -
approvedmeans resolved, not public - so without this line an anonymous request towp/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 turnedtest_get_items_type_arg_unauthenticatedred, which is a good test.Testing
tests/phpunit/tests/rest-api/rest-notes-controller.php, 37 tests / 129 assertions, covering the nine behaviours that moved plus threading, context, pagination,_fields, and the role matrix thatdata_note_get_items_permissions_data_providerused to cover.tests/phpunit/tests/comment/wpNotifyNoteMentions.phphas two tests that post and update a note over HTTP to exercise therest_insert_commentwiring. They follow notes towp/v2/notes.The remaining failure is local environment, not this branch:
Tests_Script_Modules_WpScriptModules::test_default_script_module_files_existwants built files this worktree has not generated.Test_oEmbed_Controller::test_proxy_with_classic_embed_providerjoins it whenever the run cannot reach the network.tests/qunit/fixtures/wp-api-generated.jsis regenerated for the two new routes, in its own commit.Open questions
wp/v2/comments?type=noteworks 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_commentfor the single-note route.get_item_permissions_check()here usesedit_comment, which maps toedit_poston the parent. That preserves what the comments controller did, but is worth a second look.block_editor_rest_api_preload_pathswould wantwp/v2/notesalongside 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.