Skip to content

REST API: Add parsed blocks to the post content field. - #15835

Open
adamsilverstein wants to merge 2 commits into
WordPress:trunkfrom
adamsilverstein:53603-rest-content-blocks
Open

adamsilverstein wants to merge 2 commits into
WordPress:trunkfrom
adamsilverstein:53603-rest-content-blocks

Conversation

@adamsilverstein

@adamsilverstein adamsilverstein commented Nov 8, 2026 •

Copy link
Copy Markdown
Member

Claude Code wrote this backport and its description, carried over from the Gutenberg PR:

Adds content.blocks to post type REST API responses: the post's blocks as parsed from the content, with keys that match the public properties of WP_Block (name, attributes, inner_blocks, inner_html, inner_content). This gives headless and component-based front ends the block tree without parsing the serialized markup themselves.

The blocks carry the raw stored markup, so like content.raw they are only returned in the edit context, where the controller already leaves out posts the user cannot edit. Parsing adds to every response, so they are also opt-in: the request has to name content.blocks in _fields, eg. GET /wp/v2/posts/1?context=edit&_fields=id,content.blocks. Existing clients, including the editor's own context=edit loads, don't change.

content.blocks is parsed after the same Block Hooks that content.raw gets, so the two describe the same tree. The revisions and autosaves endpoints return it as well, since their schemas already copy content from the parent post type. The conversion and the request check live in two private helpers in rest-api.php so both controllers share them.

Worth noting that attributes are only the ones stored in the block comment delimiter. Sourced attributes (eg. an image src that lives in the HTML) are not extracted; that depends on the HTML API selector work in #62653.

Gutenberg PR: WordPress/gutenberg#2649

Testing

  1. As an editor, request a post: /wp-json/wp/v2/posts/<id>?context=edit&_fields=id,content.blocks.
  2. content.blocks holds the block tree, nested blocks under inner_blocks.
  3. Leave out _fields, or request context=view, and content.blocks is not returned.
  4. Request a revision with context=edit&_fields=id,content.blocks and its blocks come back too.
  5. vendor/bin/phpunit --group 53603

The wp-api-generated.js fixture changes only add the new schema property.

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

Use of AI Tools

AI assistance: Yes
Tool(s): Claude Code
Model(s): Claude Opus 5.5
Used for: Code, tests and this description were written by Claude Code at my direction. I will review and test.

Add `content.blocks` to post type responses, holding the post's blocks
as parsed from the content, with keys matching the public properties of
`WP_Block`. Like `content.raw`, the blocks carry raw stored markup, so
they are only returned in the `edit` context. Parsing adds to every
response, so they are also opt-in: the request has to name
`content.blocks` in `_fields`.

See #53603.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018vwYjWL8VuAH7kt7zZdZi4
@github-actions

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

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

Props adamsilverstein.

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


Props Bot commands

You can trigger Props Bot actions by commenting on this PR:

  • @propsbot add <username> to give props to a GitHub user. Requires write access.
  • @propsbot remove <username> to leave a user out of the props. Requires write access.
  • @propsbot refresh to refresh this list. Adding the props-bot label works too.

@github-actions

github-actions Bot commented Nov 8, 2026

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.

Apply the same Block Hooks to the content before parsing it, so
`content.blocks` and `content.raw` describe the same tree, and return
`content.blocks` from the revisions and autosaves endpoints, whose
schemas already copy the field from the parent post type.

Move the block conversion and the request check into private helpers
in rest-api.php so both controllers share them.

See #53603.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018vwYjV3Gi53g5ct8BDL5JE
@adamsilverstein

adamsilverstein commented Nov 10, 2026 •

Copy link
Copy Markdown
Member Author

@TimothyBJacobs before this goes much further, I'd love your read on the one design choice most likely to change it: using _fields as the opt-in switch.

Claude laid out the tradeoff:

content.blocks is in the schema, but it is only returned when _fields names it literally. _fields=content does not include it, even though rest_is_field_included( 'content.blocks', array( 'content' ) ) returns true, so the patch adds an explicit wp_parse_list( $request['_fields'] ) check on top of the normal rule.

The reason is cost: parsing on every context=edit load, including the editor's own, would add a second parse and roughly double the content payload for clients that never asked for blocks.

The catch is that _fields is a response filter everywhere else in core, so this is a new contract. Clients that build _fields from the schema, or ask for the parent content, won't get what the schema advertises.

If that is a dealbreaker, the fallback is a dedicated boolean query parameter (eg. blocks=true) registered in the collection and item params, with content.blocks returned whenever it is set in the edit context.

What do you think?

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