Why my ACF snippet doesn’t run in the bulk editor

Applies to: WPMelon Advanced Bulk Edit Pro for WooCommerce

Short answer

The bulk editor writes ACF values with update_field(). It doesn’t submit ACF’s edit form, so hooks that only fire on a form submit, like acf/save_post, never run. Use acf/update_value instead. It fires on every write, whether it comes from the product edit page, the bulk editor, WP-CLI, an importer, or the REST API.

What’s going on

ACF has two kinds of hooks:

  • Form hooks, like acf/save_post. ACF fires these from its own form handler, which only runs when the edit screen submits $_POST['acf'] with an ACF nonce.
  • Value hooks, like acf/update_value and acf/load_value. ACF fires these inside update_field() and get_field(), no matter who called them.

The bulk editor is a programmatic writer. It calls update_field() for each product and each changed ACF field. That means value hooks fire, form hooks don’t.

This isn’t specific to our plugin. Any code that writes ACF values without going through the edit screen behaves the same way.

Doesn’t “Notify other plugins of changes” fix this?

No. That setting re-fires the standard WordPress save hooks after each product save, the same ones the default WooCommerce editors trigger: save_post_product (or save_post_product_variation), save_post, wp_insert_post and wp_after_insert_post. Plugins listening on those get told about the change.

ACF does listen on save_post, but its listener checks for the form data and nonce first and bails out if they aren’t there. So it gets notified and then does nothing. That’s expected ACF behaviour, and we don’t fake the form data because other listeners assume it’s real.

The fix

Hook acf/update_value for your source field. This example copies one field into another whenever the source is written:

add_filter( 'acf/update_value/name=source_field', function ( $value, $post_id ) {
    if ( get_post_type( $post_id ) === 'product' ) {
        update_field( 'target_field', $value, $post_id );
    }
    return $value;
}, 10, 2 );

Replace source_field and target_field with your ACF field names.

Two things to know:

  • Use the incoming $value, not get_field(). The filter runs before the source value is written, so get_field() would still return the old value.
  • It mirrors cleared values. If the source is emptied, the target is emptied too. If you want to keep the old target value in that case, wrap the update_field() call in if ( $value !== '' && $value !== null ).

The same filter works with a field key instead of a name: acf/update_value/key=field_abc123.

This covers grid inline edits, Bulk Edit apply and Operation History restore, for both products and variations. One exception: duplicating a product copies ACF values as plain post meta, so acf/update_value does not run for the copies.

Alternative: the plugin’s own save hook

If you specifically want to react to bulk editor saves, and only those, the plugin fires wcabe_g2_after_product_save once per product, right after a successful save. It fires for inline edits, Bulk Edit apply and Operation History restore. It does not fire for Duplicate, Create, Trash or Delete.

The first argument is the saved WC_Product object (a WC_Product_Variation for variations). The second is an array of the changes that were applied, keyed by grid field name. ACF fields appear as meta_acf_{field_name}.

add_action( 'wcabe_g2_after_product_save', function ( $product, $changes ) {
    if ( ! isset( $changes['meta_acf_source_field'] ) ) {
        return;
    }
    update_field( 'target_field', $changes['meta_acf_source_field'], $product->get_id() );
}, 10, 2 );

The ACF write happens before this hook, so reading the value back with get_field() also works here. If a listener throws, the error is caught and reported as a warning; it won’t fail the save.

For syncing ACF fields, the acf/update_value approach above is usually the better choice, because one snippet then covers the product page, the bulk editor, and any importer.

Other plugins with the same behaviour

The same distinction applies to any plugin that hooks its own form submission rather than the underlying value write. If a snippet or plugin works on the product edit page but not in the bulk editor, check whether it’s listening to a form hook. Most plugins also expose a lower-level hook you can use instead.

Support scope

We’re happy to explain why a hook does or doesn’t fire in the bulk editor. Writing or debugging custom snippets is outside what support covers.