Describe the bug
ACF renders a hidden, empty #acf_after_title-sortables meta box container on every block editor post screen, even when no field group uses the acf_after_title position. Because WordPress' postboxes.js connects all .meta-box-sortables elements as jQuery UI sortable drop targets, users can drag non ACF meta boxes into this invisible container. The drop is persisted by postboxes.save_order() into the user's meta-box-order_{$screen} user meta under an acf_after_title bucket.
On the next load, WP core re-registers those box IDs into the acf_after_title context via add_meta_box( $id, null, null, $screen, $box_context, 'sorted' ) (wp-admin/includes/template.php:1324–1332). Gutenberg only renders side/normal/advanced visibly, so the box disappears for that user, the corruption is per-user, not site wide, and is permanent because ACF's existing self repair never runs in this scenario.
To Reproduce
- Install ACF 6.8.9 with no field group set to the "High (after title)" position.
- Open any post in the block editor with at least one classic meta box present (e.g. a plugin meta box).
- Inspect the hidden
.metabox-base-form form rendered by the_block_editor_meta_boxes(), it contains an empty <div id="acf_after_title-sortables" class="meta-box-sortables"></div> output by ACF_Form_Post::edit_form_after_title() → do_meta_boxes( get_current_screen(), 'acf_after_title', $post ) (includes/forms/form-post.php:206).
- Start dragging a meta box in the editor.
assets/build/css/acf-input.css (body.is-dragging-metaboxes #acf_after_title-sortables { outline: 3px dashed; min-height: 60px; }) paints the invisible container as a visible drop target (assets/src/sass/_postbox.scss:63).
- Drop the box into the
acf_after_title drop zone.
- Reload the editor. The dropped box no longer appears anywhere on the screen, and
get_user_option( 'meta-box-order_{$screen}' ) now contains an acf_after_title bucket with the box ID.
Expected behavior
With no field groups registered at the acf_after_title position, the hidden form should contain no #acf_after_title-sortables container, so there is no droppable target and no way to corrupt the user's meta box order. Field groups actually positioned acf_after_title must continue to render, sort, and save as before, and the hidden acf_form_data() inputs (nonce/screen/post_id) must still render on every post edit screen.
Screenshots or Video
N/A
Code
No field group or block code is involved, the bug reproduces with zero ACF field groups at the acf_after_title position and any other plugin registered meta box.
Version Information:
- WordPress Version 6.9.x and later
- PHP Version 8.2
- ACF Version 6.8.9
- Browser Chrome, Safari, Firefox
Additional context
Why the existing repair never fires, WP core's the_block_editor_meta_boxes() (wp-admin/includes/post.php):
- Applies
filter_block_editor_meta_boxes (~line 2375), at this point the acf_after_title bucket only contains boxes registered at that position (i.e. ACF field groups with position acf_after_title). With none present, ACF_Form_Gutenberg::filter_block_editor_meta_boxes() never attaches its modify_user_option_meta_box_order repair filter (includes/forms/form-gutenberg.php:107–149).
- ACF's hidden-fields render (
block_editor_meta_box_hidden_fields → ACF_Form_Post::edit_form_after_title()) is where the first do_meta_boxes() call happens core re-registers box IDs found under acf_after_title in the user's saved order.
- Only
side/normal/advanced render visibly afterwards, then the box is gone.
Root causes:
includes/forms/form-post.php, ACF_Form_Post::add_meta_boxes() unconditionally adds add_action( 'edit_form_after_title', ... ) (~line 171), and edit_form_after_title() calls do_meta_boxes( ..., 'acf_after_title', ... ) with no guard for whether anything exists in that context. do_meta_boxes() always outputs the <div id="{context}-sortables" class="meta-box-sortables"> wrapper, even when empty.
includes/forms/form-gutenberg.php, block_editor_meta_box_hidden_fields() re-runs the same unguarded render inside the hidden form.
Suggested fix (render gating): in ACF_Form_Post::edit_form_after_title(), guard the do_meta_boxes( ..., 'acf_after_title', ... ) call on isset( $wp_meta_boxes[ get_current_screen()->id ]['acf_after_title'] ), keeping acf_form_data() unconditional. This removes the empty drop target while preserving rendering when the context genuinely has content, and works for both the classic and block editors since both paths call the same method.
Describe the bug
ACF renders a hidden, empty
#acf_after_title-sortablesmeta box container on every block editor post screen, even when no field group uses theacf_after_titleposition. Because WordPress'postboxes.jsconnects all.meta-box-sortableselements as jQuery UI sortable drop targets, users can drag non ACF meta boxes into this invisible container. The drop is persisted bypostboxes.save_order()into the user'smeta-box-order_{$screen}user meta under anacf_after_titlebucket.On the next load, WP core re-registers those box IDs into the
acf_after_titlecontext viaadd_meta_box( $id, null, null, $screen, $box_context, 'sorted' )(wp-admin/includes/template.php:1324–1332). Gutenberg only rendersside/normal/advancedvisibly, so the box disappears for that user, the corruption is per-user, not site wide, and is permanent because ACF's existing self repair never runs in this scenario.To Reproduce
.metabox-base-formform rendered bythe_block_editor_meta_boxes(), it contains an empty<div id="acf_after_title-sortables" class="meta-box-sortables"></div>output byACF_Form_Post::edit_form_after_title()→do_meta_boxes( get_current_screen(), 'acf_after_title', $post )(includes/forms/form-post.php:206).assets/build/css/acf-input.css(body.is-dragging-metaboxes #acf_after_title-sortables { outline: 3px dashed; min-height: 60px; }) paints the invisible container as a visible drop target (assets/src/sass/_postbox.scss:63).acf_after_titledrop zone.get_user_option( 'meta-box-order_{$screen}' )now contains anacf_after_titlebucket with the box ID.Expected behavior
With no field groups registered at the
acf_after_titleposition, the hidden form should contain no#acf_after_title-sortablescontainer, so there is no droppable target and no way to corrupt the user's meta box order. Field groups actually positionedacf_after_titlemust continue to render, sort, and save as before, and the hiddenacf_form_data()inputs (nonce/screen/post_id) must still render on every post edit screen.Screenshots or Video
N/A
Code
No field group or block code is involved, the bug reproduces with zero ACF field groups at the
acf_after_titleposition and any other plugin registered meta box.Version Information:
Additional context
Why the existing repair never fires, WP core's
the_block_editor_meta_boxes()(wp-admin/includes/post.php):filter_block_editor_meta_boxes(~line 2375), at this point theacf_after_titlebucket only contains boxes registered at that position (i.e. ACF field groups with positionacf_after_title). With none present,ACF_Form_Gutenberg::filter_block_editor_meta_boxes()never attaches itsmodify_user_option_meta_box_orderrepair filter (includes/forms/form-gutenberg.php:107–149).block_editor_meta_box_hidden_fields→ACF_Form_Post::edit_form_after_title()) is where the firstdo_meta_boxes()call happens core re-registers box IDs found underacf_after_titlein the user's saved order.side/normal/advancedrender visibly afterwards, then the box is gone.Root causes:
includes/forms/form-post.php,ACF_Form_Post::add_meta_boxes()unconditionally addsadd_action( 'edit_form_after_title', ... )(~line 171), andedit_form_after_title()callsdo_meta_boxes( ..., 'acf_after_title', ... )with no guard for whether anything exists in that context.do_meta_boxes()always outputs the<div id="{context}-sortables" class="meta-box-sortables">wrapper, even when empty.includes/forms/form-gutenberg.php,block_editor_meta_box_hidden_fields()re-runs the same unguarded render inside the hidden form.Suggested fix (render gating): in
ACF_Form_Post::edit_form_after_title(), guard thedo_meta_boxes( ..., 'acf_after_title', ... )call onisset( $wp_meta_boxes[ get_current_screen()->id ]['acf_after_title'] ), keepingacf_form_data()unconditional. This removes the empty drop target while preserving rendering when the context genuinely has content, and works for both the classic and block editors since both paths call the same method.