Bulk Actions
Bulk actions – is a part of WordPress admin interface, that allows you to select multiple elements like posts, terms, users and perform any action to all of them.
It is very handy because for example it allows you to remove let’s say hundreds of posts in just three clicks and saves you tons of time. Check the screenshot below to see how it works.

In this tutorial I am going to talk about how to remove default bulk actions and how to create a custom one. But first of all we will take a look at the hooks.
Maybe it is also worth noticing that bulk actions API is available for us since WordPress 4.7 version.
Bulk Action Hooks
The whole bulk action coding thing comes down to 2 action hooks:
bulk_actions-{screen id}
– allows to remove default bulk action options and to add a custom one into the dropdown.handle_bulk_actions-{screen id}
– when creating a custom bulk action, you have to process elements somehow. And you can do it inside this hook.
As you can see, both hooks have a screen ID as a part of it. You can find out it on every WordPress admin page by yourself using get_current_screen()
function but you can also take a look at the table below.
Hooks | Admin pages |
---|---|
bulk_actions-edit-post handle_bulk_actions-edit-post | Posts |
bulk_actions-upload handle_bulk_actions-upload | Media (List view) |
bulk_actions-edit-page handle_bulk_actions-edit-page | Pages |
bulk_actions-edit-{CPT name} handle_bulk_actions-edit-{CPT name} | Custom post types |
bulk_actions-edit-category handle_bulk_actions-edit-category | Categories |
bulk_actions-edit-post_tag handle_bulk_actions-edit-post_tag | Tags |
bulk_actions-edit-{Taxonomy name} handle_bulk_actions-edit-{Taxonomy name} | Custom taxonomies |
bulk_actions-edit-comments handle_bulk_actions-edit-comments | Comments |
bulk_actions-plugins handle_bulk_actions-plugins | Plugins |
bulk_actions-users handle_bulk_actions-users | Users |
Remove Default Bulk Actions
Before creating our custom bulk actions let’s try to remove some of the default ones. You can do it with the help of the first one in the pair of hooks I mentioned before – bulk_actions-{screen id}
, inside the hook we are going to operate with an array of bulk actions. In order to remove a bulk action we just have to remove it from the array.
/*
* Remove a Default Bulk Action
*
* @author Misha Rudrastyh
* @link https://rudrastyh.com/wordpress/custom-bulk-actions.html#remove-bulk-actions
*/
add_action( 'bulk_actions-edit-post', 'rudr_remove_default_bulk_actions' );
function rudr_remove_default_bulk_actions( $bulk_array ) {
unset( $bulk_array[ 'edit' ] );
return $bulk_array;
}
A couple of moments:
- If you don’t know where to insert the code from this tutorial, you can paste it into your current theme
functions.php
file. But in case you theme receive updates, consider a Child theme’sfunctions.php
or a custom plugin. - You might ask where did I get
$bulk_array[ 'edit' ]
in the code above? You can find it out by inspecting the bulk actions dropdown in your browser or just useprint_r()
function to inspect$bulk_array
array.
There we go:

Create Custom Bulk Actions
Good news for you guys, I decided to add two custom bulk actions:
- the first one will operate with post statuses,
- the second one – with custom fields.
<?php
/**
* Create Custom Bulk Actions for WordPress Posts
*
* @author Misha Rudrastyh
* @link https://rudrastyh.com/wordpress/custom-bulk-actions.html
*/
// add to dropdown
add_filter( 'bulk_actions-edit-post', 'rudr_my_bulk_actions' );
function rudr_my_bulk_actions( $bulk_array ) {
$bulk_array[ 'misha_make_draft' ] = 'Make draft';
$bulk_array[ 'misha_set_price_1000' ] = 'Set price to $1000';
return $bulk_array;
}
// process the action
add_filter( 'handle_bulk_actions-edit-post', 'rudr_bulk_action_handler', 10, 3 );
function rudr_bulk_action_handler( $redirect, $doaction, $object_ids ) {
// let's remove query args first
$redirect = remove_query_arg(
array( 'bulk_make_draft', 'bulk_price_changed' ),
$redirect
);
// do something for "Make Draft" bulk action
if ( 'misha_make_draft' === $doaction ) {
foreach ( $object_ids as $post_id ) {
wp_update_post(
array(
'ID' => $post_id,
'post_status' => 'draft' // set status
)
);
}
// do not forget to add query args to URL because we will show notices later
$redirect = add_query_arg(
'bulk_make_draft', // just a parameter for URL
count( $object_ids ), // how many posts have been selected
$redirect
);
}
// do something for "Set price to $1000" bulk action
if ( 'misha_set_price_1000' === $doaction ) {
foreach ( $object_ids as $post_id ) {
update_post_meta( $post_id, 'product_price', 1000 );
}
$redirect = add_query_arg(
'bulk_price_changed',
count( $object_ids ),
$redirect
);
}
return $redirect;
}
// display messages
add_action( 'admin_notices', 'rudr_bulk_action_notices' );
function rudr_bulk_action_notices() {
// first of all we have to make a message,
// of course it could be just "Posts updated." like this:
if( ! empty( $_REQUEST[ 'bulk_make_draft' ] ) ) {
?>
<div class="updated notice is-dismissible">
<p>Posts updated.</p>
</div>
<?php
}
// but you can create an awesome message
if( ! empty( $_REQUEST[ 'bulk_price_changed' ] ) ) {
$count = (int) $_REQUEST[ 'bulk_price_changed' ];
// depending on ho much posts were changed, make the message different
$message = sprintf(
_n(
'Price of %d product has been changed.',
'Price of %d products has been changed.',
$count
),
$count
);
echo "<div class=\"updated notice is-dismissible\"><p>{$message}</p></div>";
}
}
Some notes:
- You do not have to create a callback for each bulk action, you can do it in a single
handle_bulk_actions-{screen id}
hook. Just do not forget to replace{screen id}
below if you want to add bulk actions not for posts. - When creating admin notices, you can use something like “The posts updated” or create a more detailed message with the help of
_n()
(for plural forms) andsprintf()
functions.
That’s how it looks:


Misha Rudrastyh
Hey guys and welcome to my website. For more than 10 years I've been doing my best to share with you some superb WordPress guides and tips for free.
Need some developer help? Contact me
wow. another impressive sharing ! thanks
Always welcome 🙂 thank you for your feedback!
Misha, I love your tutorials! Thanks a lot! I’d like to know if us use any online tool to create your animated gifs. I saw one with only 200kb. The gifs I try online all save above 1MB. :(
Hi Elisandro,
I use Photoshop 🙃
Cool. Thanks for the reply! :)
Perfect! Thanks Misha
Thank you so much for your code!
I’ve used it to bulk edit the “status” of a custom post type “Message”; messages are being generated with a front end form (powered by the Plugin “Advanced Custom Fields”) and the “status” (a custom field) helps us keep track of whether we already replied or not.
The code dynamically reads out all the available options for a message and creates a bulk edit option for each. I also added yet another request value so we can display the new status for our messages after bulk editing them. :-)
Hope this can help somebody else just like Misha’s code made my life much easier.
https://gist.github.com/d2fe594b8d392a262a3bb76088ac568b
Thank you! :)
Hi Misha,
Great tutorial. Thanks!
Do you know how to pass an POST ID variable to each item in a custom $bulk_array?
I’d like to link posts from a custom post type (which acts like categories in my system) to the posts to $bulk_array (easy with get_posts or WP_Query). Then in the do_action, I’d like to set
update_post_meta( $post_id, 'product_category', POST_ID );
Using your example, I’d like to somehow add the variable POST_ID to
$bulk_array['misha_set_category_to_tshirt'] = 'Set category to tshirt';
And then
update_post_meta( $post_id, 'product_category', POST_ID );
Maybe a way is to make $bulk_array multidimensional?
$bulk_array['misha_set_category_to_tshirt'][POST_ID] = 'Set category to tshirt';
FYI:
$bulk_array['set_category_test'] = array('Set category', 233);
didn’t work:-)
Hey Nicolas,
Thank you for a perfect question!
The first idea that came to my mind is to use actions like
set_category_233
, I mean to use IDs as a part of action names.