No, you did nothing wrong.
It’s a known issue and in WordPress v6.1.3 and up to the current stable release as of writing (v6.2.2), it’s happening because of the following lines in get_the_block_template_html()
which returns the markup for the current (block-based) template:
$content = do_shortcode( $content );
$content = do_blocks( $content );
So as you could see, shortcodes in the post content (post_content
) are being run before the blocks are parsed, which means that by the time your shortcode runs, the Query Loop block has not yet run, hence get_the_ID()
, get_post()->ID
, etc. would return the ID of the first post or whatever is the current post in the current loop.
In the past, it used to be that do_blocks()
is being called before do_shortcode()
, then in WordPress v6.1.2, the do_shortcode()
call was removed and yet, added back in v6.1.3 (see changesets 55771 and 55830 in Trac), so I don’t know if it will ever be back to any of that behaviors.
Nonetheless, there are 2 possible workarounds you can try:
-
In your shortcode function, return a special tag like
%xyz_shortcode%
, and use a filter hook likerender_block
orrender_block_<name>
to replace that tag with the “real” content.See example here.
-
Create or turn your shortcode into a dynamic block, i.e. a block type with a
render_callback
.See examples here.
Note that I’m consuming a block context named
postId
which gives us the correct ID of the current post in the Query Loop. Seexyz-block-csr/index.jsx
andxyz-block-ssr/index.jsx
to see how I used that context.