Above most tables of elements in WordPress admin (e.g. posts, pages, comments, plugins, and users) you will find WordPress’ functionality for bulk actions. Bulk actions allow you to check off multiple elements, choose an action and have that action performed for all elements at once. In this post we’ll look at how to add our own custom bulk actions to WordPress admin.

Adding a custom bulk action has become a whole lot easier in recent versions of WordPress (since 4.7). There’s a filter for the options available in the dropdown; another filter for what it should do when your option is chosen, and another hook for optionally showing an admin notice. Let’s go through each one of these.

Adding a custom bulk action in the dropdown

Let’s start with adding our custom bulk choice to the bulk action dropdown. For this we use the filter bulk_actions_<screen>, where you replace <screen> with whichever admin screen you want your option to be added to. Here’s a complete overview of possible options for screen:

  • bulk_actions-edit-post: Post type ‘post’ edit screen
  • bulk_actions-edit-page: Post type ‘page’ edit screen
  • bulk_actions-edit-<post-type-name>: Custom post type edit screen
  • bulk_actions-edit-<custom-taxonomy>: Custom taxonomy edit screen
  • bulk_actions-edit-comments: Comments list screen
  • bulk_actions-plugins: Plugins list screen
  • bulk_actions-users: Users list screen
  • bulk_actions-upload: Media library list (only works in list view, not grid view)

The filter is applied onto an array with key-value pairs; where keys are an unique key for actions, and values are the label that appears in the dropdown.

Let’s look at an example. I want to create a bulk action for setting posts to published.

add_filter('bulk_actions-edit-post', function($bulk_actions) {
	$bulk_actions['change-to-published'] = __('Change to published', 'txtdomain');
	return $bulk_actions;
});

If you save and refresh posts edit screen, you should see your option in the dropdown.

As of right now our custom bulk action is doing absolutely zip. That’s the next step.

Making our custom bulk action do something

In order to trigger something when choosing our newly added option in the dropwon we hook onto the filter handle_bulk_actions-<screen>. Refer to the overview above for possible screen values. Obviously your handle hook should be the same screen as the screen in which you added the bulk action in the first step.

You have three possible arguments in this filter. The first – the one you return – is actually an URL that WordPress should redirect to after it’s finished with your bulk action. Second argument is the name of the action that was chosen in the dropdown. And third is an array of all element IDs that were checked for the bulk action. These are the elements we need to apply our action onto.

add_filter('handle_bulk_actions-edit-post', function($redirect_url, $action, $post_ids) {
	if ($action == 'change-to-published') {
		foreach ($post_ids as $post_id) {
			wp_update_post([
				'ID' => $post_id,
				'post_status' => 'publish'
			]);
		}
		$redirect_url = add_query_arg('changed-to-published', count($post_ids), $redirect_url);
	}
	return $redirect_url;
}, 10, 3);

Let’s look at what the above code does step by step. First we need to check if the bulk action performed was indeed our custom action; change-to-published. And then it’s up to us to do whatever we want with all the IDs.

The code above loops through all post IDs and performs wp_update_post() on each in order to change their post status to published. (If you want this to be efficient you might want to consider only updating those that indeed are not already published).

After we’re done doing our action on the selected elements, we need to build an URL to redirect to after WordPress is done. We add a custom argument to the provided redirect URL. This is optional but necessary if we want to show an admin notice. We can add anything you want. But in the example above we are simply adding a new query arg with add_query_arg() setting ‘changed-to-published‘ to the number of post IDs that were affected. This is useful information for a notice.

Show a notice after our custom bulk action is done

If you perform our custom bulk action now, you will notice that after WordPress has completed the bulk action, it reloads the page with the following in the URL: “wp-admin/edit.php?changed-to-published=2”. “2” is the number of posts we applied our action onto. This allows us to add a custom admin notice which triggers if ‘changed-to-published’ is set.

In order to show admin notices, we can use the action admin_notices. We check PHP’s global variable, $_REQUEST, that are populated with form submitted values if ‘changed-to-published‘ exists. If it does, that means our custom bulk action was just performed. We will then display some text including the number of posts applied.

add_action('admin_notices', function() {
	if (!empty($_REQUEST['changed-to-published'])) {
		$num_changed = (int) $_REQUEST['changed-to-published'];
		printf('<div id="message" class="updated notice is-dismissable"><p>' . __('Published %d posts.', 'txtdomain') . '</p></div>', $num_changed);
	}
});

And that’s it!

Other uses of custom bulk action

Keep in mind that in the handle_bulk_actions-<screen> hook you can do basically anything you want onto the selected elements. You can update custom post meta, change roles on users, send an email, or post a HTTP request to some third party app.

For example; if you wanted a bulk action to update a custom post meta that informs whether or not posts are verified, using a custom bulk action with key ‘mark-as-verified‘:

add_filter('handle_bulk_actions-edit-post', function($redirect_url, $action, $post_ids) {
	if ($action == 'mark-as-verified') {
		foreach ($post_ids as $post_id) {
			update_post_meta($post_id, 'verified', '1');
		}
		$redirect_url = add_query_arg('mark-as-verified', count($post_ids), $redirect_url);
	}
	return $redirect_url;
}, 10, 3);

Combine the above with a custom column that shows the value of ‘verified’ in posts lists – like we did in this post, and it gets extra useful.