Add a WordPress Custom Bulk Action

Good news! There are now core APIs to do this as of WordPress 4.7. You can read more here.

Like me you probably woke up this morning assuming that adding a new bulk action to WordPress would be as easy tracking down the correct filter or implementing the right action hook. If so, then you might have had a rude awakening when you attempted to make use of the promising-looking filter named bulk-actions-screenid and failed. Thanks to a couple of posts on the subject I was able to create my own custom bulk actions, however it took enough of my time that I decided to put together a little article on the subject, and even include a full-fledged working demo plugin to hopefully make your own bulk actions that much easier to get going.

The Goal

The goal here is to describe and provide code that allows you to easily add your own custom bulk actions to any part of the WordPress admin: posts, media, users, whatever. The approach presented is based on this wordpress answers page. For this example, we’ll pretend we’re adding a custom action named “Export” to the posts page, to allow the bulk exporting of selected posts. Note that if you want to add custom bulk actions to one of the other sections, say the Users page, you’ll have to modify my code in a couple of places which I identify in the demo plugin comments. Our final result should look like this:

The Issue

That promising-looking filter bulk-actions-screenid currently only has the ability to remove bulk actions, not add them. This is clearly noted on the filters codex page, and in a code comment in the file wp-admin/includes/class-wp-list-table.php. As described in this trac ticket the issue isn’t so much how to add a new bulk action to the dropdown menu, as it is how to generically handle custom bulk actions. Since this is yet to be solved in the core, it requires a bit of hacking; however don’t worry no core files were harmed in the making of this plugin.

The Solution

Enough of the problems, on to the solution. Creating a custom bulk action breaks down to three components. First, adding an item to the bulk action select box:

add_action('admin_footer-edit.php', 'custom_bulk_admin_footer');

function custom_bulk_admin_footer() {

  global $post_type;

  if($post_type == 'post') {
    <script type="text/javascript">
      jQuery(document).ready(function() {
        jQuery('<option>').val('export').text('<?php _e('Export')?>').appendTo("select[name='action']");
        jQuery('<option>').val('export').text('<?php _e('Export')?>').appendTo("select[name='action2']");

As you can see, here we use javascript to inject our new bulk action item into the two bulk action select dropdowns on the Posts edit page. This piece of code certainly isn’t poetry, but until a filter capable of adding items is added to the core, it will have to do.

The next step is the code that performs the bulk action. This is done with a hook on the load-* actions, as recommended in the Q&A page referenced above. Even with demo code, this function is fairly lengthy so I’ll leave most of it out, please reference my demo plugin for the full working implementation. Its main tasks are to:

  1. determine the action
  2. perform security checks
  3. perform the action
  4. redirect the client to a results page
add_action('load-edit.php', 'custom_bulk_action');

function custom_bulk_action() {

  // ...

  // 1. get the action
  $wp_list_table = _get_list_table('WP_Posts_List_Table');
  $action = $wp_list_table->current_action();

  // ...

  // 2. security check

  // ...

  switch($action) {
    // 3. Perform the action
    case 'export':
      // if we set up user permissions/capabilities, the code might look like:
      //if ( !current_user_can($post_type_object->cap->export_post, $post_id) )
      //  pp_die( __('You are not allowed to export this post.') );

      $exported = 0;

      foreach( $post_ids as $post_id ) {
        if ( !$this->perform_export($post_id) )
          wp_die( __('Error exporting post.') );

      // build the redirect url
      $sendback = add_query_arg( array('exported' => $exported, 'ids' => join(',', $post_ids) ), $sendback );

    default: return;

  // ...

  // 4. Redirect client


The third step is to display a notice describing the result of the action by hooking into admin_notices and rendering a message if we have the ‘exported’ request parameter that was set in the previous block of code:

add_action('admin_notices', 'custom_bulk_admin_notices');

function custom_bulk_admin_notices() {

  global $post_type, $pagenow;

  if($pagenow == 'edit.php' && $post_type == 'post' &&
     isset($_REQUEST['exported']) && (int) $_REQUEST['exported']) {
    $message = sprintf( _n( 'Post exported.', '%s posts exported.', $_REQUEST['exported'] ), number_format_i18n( $_REQUEST['exported'] ) );
    echo "<div class="updated"><p>{$message}</p></div>";

Which in action looks like:

Demo Plugin

And there you have it. Feel free to download and use as a starting point my demo plugin which has code similar to the above, but fully implemented and commented. Install like any other plugin, by uploading through the Admin, or by unzipping the plugin folder and placing in wp-content/plugins/. Activate the plugin, then go to Posts > All Posts and try out the ‘Export’ bulk action.

Download FoxRunSoftware Custom Bulk Action Demo Plugin

If you have any issues, or suggestions to improve the code, please leave a comment below.


  • Matth Jenks 5 years ago

    Wow, thanks for sharing this. I really didn’t expect a google search to turn up anything at all.

    • justin 5 years ago

      You’re welcome, it was a big pain for me to figure out the first time, so my hope is that the next person doesn’t have to waste half a day on it.

      • ravin 4 years ago

        Bless you.

      • Renan Emigdio 2 years ago

        I love you <3

  • nate 5 years ago

    My goal is to find code that allows me to bulk email data – Say I have columns (project) (link to project page )(contact_name) (contact_email) (artist_assigned) (art_due_date) (in-hands_date)(project_client_status)(in_house_status)… I would like to bulk email data from table to both client and artist – the client would receive a pre formatted email that sends automatically with status changes and also a bulk email option that simply can be sent out manually by selecting projects and applying bulk action… The other email would be to artists who are say invited to take on a project and then they can actually select their name from drop-down inside the table (artist_assigned) and assign themselves… I know this might be a huge task for me – a beginner – but i have most everything but the email action figured out ( any clues or posts you can point me to ? ) – sorry so long! Perhaps I just need to get to reading and looking into more PHP training… : )

    • justin 5 years ago

      Hey Nate, that is a pretty substantial project you’re describing, and you may well find that you need to learn some more PHP. Hopefully you should be able to follow and replicate the steps I describe above, and then in your action function you can send emails using the wp_mail() function. Best of luck with your project!

      • nate 5 years ago

        great – So I’m still researching and my answer so far is to try using Piklist when ready – Have you played with Piklist yet? Looks promising! Of course some php knowledge will go a long way when this application builder framework is stable!

        • justin 5 years ago

          Nope, never heard of Piklist, hopefully it works out for you! I’d be curious to hear back from you here if it does, it would be good to know it’s worth recommending to others. Good luck!

  • Matth Jenks 5 years ago

    Nate, you might want to look into Project Management plug-ins for wordpress. Some of them, like CollabPress, have the ability to e-mail certain collaborators on a project at each milestone.

    Another option is to set up a Basecamp account. It’s not free but it might be easier and cheaper than developing your own custom app.

  • Matth Jenks 5 years ago

    There’s also this plugin which looks promising:

  • Todd Crowe 5 years ago

    This is a great article. It was exactly what I was looking for and saved me a lot of time. Thank you!

    • justin 5 years ago

      You are very welcome Todd, glad it helped!

  • Matteo 5 years ago

    Hello there, thanks for your precious tutorial!

    Anyway I have a question for you… I see that “admin_footer-edit” hookworks well for posts, but what if I need to add my custom bulk action to the media gallery list (“upload.php”)? Which hook should I use? Tried a million google searches with no success…

    Thanks in advance 🙂

    • justin 5 years ago

      Hey Matteo you would hook into the 'admin_footer-upload.php' action, and if you’re checking the $post_type as I do, it would be 'attachment'

      • Jay 5 years ago

        I really appreciate this tutorial. It seems to be about the only thing on the web for doing this–all the other places online link back here.

        Like Matteo, I also am trying to adapt your excellent code for the media page. I got it to work there using the adaptations you suggest here and in the file itself (v0.1, 4/4/2012), but only if I comment out a conditional (L58) and the security check (L68) in `custom_bulk_action()`, viz.

        `if($post_type == ‘post’) {`


        I tried replacing `’post’` with `’attachment’`, but it turns out that the problem is that `$post_type = $typenow` is always false, even if the operation (without the conditional) runs successfully.

        Any ideas on how I can get these lines to work?

        • Max G J Panas 4 years ago

          Hey, the security checks are Always important if you want to minimize misuse of the bulk action. So you’re going to want to keep them around. For the media page, here’s how:

          change the conditional to:
          if ( ! isset( $_REQUEST[‘detached’] ) ) {

          and the sec check to:
          check_admin_referer( ‘bulk-media’ );

        • Jan de Baat 2 years ago

          Just found out that the test on post_type might fail because sometimes post_type is an array of values.
          You should make an array of post_type if it is not and then use in_array() to test for the correct post_type.

  • Andrew 5 years ago

    Add a bulk action without JavaScript:

    global $wp_list_table;

    $wp_list_table->_actions = array_merge(


    array(‘new_action’ => __(‘My new action’))


    • Jay 5 years ago

      Hi Andrew,

      Interesting comment–could be very helpful. Could you supply a little context? I’ve naively just replaced the contents of custom_bulk_admin_footer() with what you’ve written and it doesn’t work.

      It would be appreciated.


      • Max G J Panas 4 years ago

        Hey Jay,

        you are going to need to add the custom bulk upload before the table gets built, so putting it in admin footer like you tried is a no go.

        Instead, you can hijack the ‘admin_head’ action and put the code in there. $wp_list_table is defined by this point so adding to it like so is cake.

        • Jepser Bernardino 3 years ago

          Sir, so much thanks, it was thinking that the Javascript option wan’t the best way to do it.

          • Jepser Bernardino 3 years ago

            Just a problem, it converts the action and action2 in action_2, so bulk actions don’t work.

    • raj 3 years ago

      whats the changes we have to make to implement custom-bulk-action on the users page


      • raj 3 years ago

        whats are things to be changed to implement bulk action in user page

  • Ayoub 5 years ago

    Since post_ids are in the URL …&post[]=13660&post[]=13613&post[]=13608&post[]=13564…

    This is how you get it:

    $post_ids = $_GET[‘post’];

  • obmerk99 4 years ago

    Your Zip file for the plugin returns 404

    • Max Rice 4 years ago

      Fixed now, thanks 🙂

  • Ross McKay 4 years ago

    Thanks, mate! A big help on a project I just completed. cheers!

  • Leo 4 years ago

    This was a life-saver. I used it on my theme, Symbiostock.

  • vik 4 years ago

    Thanks, great work!

  • Hugh Lashbrooke 4 years ago

    Thanks for this guide – your demo plugin got me sorted out in no time.

    • Justin Stern 4 years ago

      Glad to hear it!

  • Miguel 4 years ago

    Hi, Justin. Thanks for the article. I am really really new to this, and I am trying to learn as much as I can before venturing into asking dumb questions. But, here it goes: in your example, you create a bulk action item to be displayed in the dropdown menu. Now, after you select several posts and click on Export, what happends exactly to those posts. If this question is too basic, just direct me to another resource on the web, please. Thanks for your help.

    • Max Rice 4 years ago

      The post IDs of the posts you selected are included in the $_POST variable so that your code can perform some action with each of them. Hope that helps!

  • mk 4 years ago


    I am getting empty in this $action = $wp_list_table->current_action(); with wp 3.7.1
    Could you tell me what I should check?
    This is the $_POST I get after I click the apply button.
    array(8) {
    string(6) “test”
    string(3) “all”
    string(4) “post”
    string(6) “201312”
    string(1) “0”
    string(1) “1”
    string(4) “list”
    array(2) {
    string(6) “440089”
    string(6) “440090”

    • Gwyneth Llewelyn 3 years ago

      Strange, there seems not to be any “action”/”action2” command being sent…

      • Justin Stern 3 years ago

        hmm, weird, it certainly should be

  • Toptan Karanfil 4 years ago

    Thank you very useful information

  • Tara 4 years ago

    Hello! Thank you so much for this code.

    I do have a question though. How do I change this so that it works on the users page? I tried very hard but I couldn’t figure out what to change the commented sections to. Thank you!

  • Marcel de Hoog 4 years ago

    Seems no longer working in 3.7.1 🙁

  • Robert Ruby II 4 years ago

    Thanks a bunch. Developing a management system for my engineering and construction company using wp as an SDK.. Need to pull reports for clients of db and this little nugget right here made my life easier… was about to say the hell with it and create a new list page just for jobs… (main custom post type in program)

    • Beka Rice 4 years ago

      happy to help!

    • Marcel de Hoog 4 years ago

      Robert did you get this to work? If so which version are you using, as in 3.7.1 I cannot get it to work at all. $wp_list_table->current_action() does return empty.

  • Manish 3 years ago

    $wp_list_table->current_action() does not return anything and when I call $wp_list_table->get_bulk_actions() then return only the previous options value..
    Please help me…

    • Max Rice 3 years ago

      Where are you hooking in that $wp_list_table->current_action() doesn’t return anything?

  • Matthew 3 years ago

    I am trying to do this but I would like to make it disable the plugin updates and than have another to enable them again. Is this possible? If so, how do I do it?

  • Norman 3 years ago

    Hi, Seems to be a great plugin. I would like to add “EMAIL” instead of “Export”. Can you tell me what t change in the plugin for it to work.
    Thanks for your hard work

    • Beka Rice 3 years ago

      Hey Norman! Doing this for emails would be a different implementation that would take far more to explain than we could in a comment, but I’ve added this to our post idea board for a future tutorial!

  • Antonie 3 years ago

    Very good article, excellent.

    I used this to build several bulk actions under the Users section of WordPress such as exporting, bulk updating user meta with a value, etc.

    Thanks again!

    • Leo DeBruyn 3 years ago

      I am trying to adapt this to work with the Users page as well.
      Can you give me any pointers on what items need to be changed?

  • Mario 3 years ago


    How do I actually EXPORT the data? I can’t seem to get any file to download…

  • sakib 3 years ago

    $wp_list_table->current_action() returns empty, so this solution won’t work for current version of wordpress(3.9). Maybe adding query variable will work instead.

    I will let you know if I can get it to work.

    • sakib 3 years ago

      After further investigation, I came to the conclusion that your code works alright.

      The problem that many people(including myself) face is that, nothing is printed if they try to print $action or any other text. The reason for that is there is another redirect there.

      If you look at wp-includes/edit.php, you will see that there is something like this

      Hope this will help others.

  • sakib 3 years ago

    There is a wp_redirect($sendack) at the end of this script. How did I miss that!

  • Tim 3 years ago

    Thanks Justin, this was very helpful.

  • Mohammad Tanveer 3 years ago


    How can I use this bulk action plugin in users page where these is no post type kind of thing?


  • Anders 2 years ago

    I can’t seem to grasp how one can use wc_add_notice() before the redirect in the bottom of the function custom_bulk_action(). I keep getting an error: Fatal error: Call to undefined function wc_add_notice()

    I won’t to show messages and then redirect. Can this be done?

    Thank you so much for a nice tutorial!

  • Andrew 2 years ago

    Can this code be updated to apply the bulk action to all items?

    I know you can change the Number of Items Per Page setting in Screen Options, but I then run into “URL too long” errors when running the bulk action.

    • Max Rice 2 years ago

      I think if you wanted a bulk action to apply to all items of a specific post type, then you’d have to create a custom action handler to first find all those items, then apply the bulk action.

  • vee 2 years ago

    How to verify nonce in users.php bulk actions?

Hmm looks like this article is over 2 years old! Its content may be outdated, so comments are now closed.