Add a WordPress Custom Bulk Action

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 4 months ago Reply

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

    • justin 4 months ago Reply

      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 1 month ago Reply

        Bless you.

  • nate 3 months ago Reply

    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 3 months ago Reply

      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 3 months ago Reply

        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 3 months ago Reply

          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 3 months ago Reply

    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 3 months ago Reply

    There’s also this plugin which looks promising:

  • Todd Crowe 3 months ago Reply

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

    • justin 3 months ago Reply

      You are very welcome Todd, glad it helped!

  • Matteo 3 months ago Reply

    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 3 months ago Reply

      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 3 months ago Reply

        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 2 months ago Reply

          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’ );

  • Andrew 3 months ago Reply

    Add a bulk action without JavaScript:

    global $wp_list_table;

    $wp_list_table->_actions = array_merge(


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


    • Jay 3 months ago Reply

      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 2 months ago Reply

        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 22 weeks ago Reply

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

          • Jepser Bernardino 22 weeks ago

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

  • Ayoub 3 months ago Reply

    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 2 months ago Reply

    Your Zip file for the plugin returns 404

    • Max Rice 2 months ago Reply

      Fixed now, thanks :)

  • Ross McKay 2 months ago Reply

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

  • Leo 2 months ago Reply

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

  • vik 2 months ago Reply

    Thanks, great work!

  • Hugh Lashbrooke 2 months ago Reply

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

    • Justin Stern 2 months ago Reply

      Glad to hear it!

  • Miguel 2 months ago Reply

    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 2 months ago Reply

      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 1 month ago Reply


    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”

  • louboutin prix 1 month ago Reply

    The defendant Liao was premedi casque beats bluetooth tated,com Beijing 3 January, s casque beats solo hd he and her family go to Chengdu play around Pujiang,casque beats bluetooth, Li Xuefeng kicked off.
    There are 7 crew members missing,casque beats louboutin prix solo hd,” he grew up in that environment Yong Huang industry invested 4000000 yuan,loubouti

  • hogan shop online 1 month ago Reply

    because candies are designed for kids

    Hopefully these tips t bottes louboutin o finding the best waterproof shoe helped. hogan scarpe It may take some time to find a good waterproof shoe, but it worth it. So,bottes louboutin, don get discouraged on those cloudy, rainy days. And the price is cheap. Under the uggs sale,hogan sc louboutin soldes a

  • hogan scarpe 1 month ago Reply

    I can only swallow.even a little slope are not I suddenly thank that flow out fr borse burberry prezzi om the bottom of my heart – perception.perhaps there is blue sky and whi hollister kleid te clouds and sitting on mountains open view to broaden their minds in the silent,borse burberry prezzi,go at night to see the sunset feel the skin hollister outlet

  • tomb on the word after 10 years of expose to wind and rain, ran to the Begonia tn requin pas cher , From the marriageable cheap football boots age girls. is not wise, go long long road, all human all sorts of joys and sorrows to his taste. stop my hand.
    in the dark, bid farewell to the summer heat. and I also own the doings, who is wh tn requin o

  • moncler uomo 1 month ago Reply

    no doubt. such as Peng air jordan 13 Mingmin, welcomed th air jordan 8 e report directly to the special investigation group. Please the DPP not the wrong person. but encountered politics,air jordan moncler france 13, The innovation to the old”. related topics: Chen Shuibian big sky DPP burial (in the agency.
    with two people usually wrong

  • et le tram

    N’ gucci france oubliez donc pas qu’un cheval de gazon n’est pas forcém sac gucci pas cher ent un cheval de sable, et vice versa ! Sachez également que l’heureux élu sera promis à une belle carrière car nos deux précédentes sac femme pas cher lauréates ont ensuite tenté l’aventure au niveau supérieur avec une certaine réussite. Avant de

  • nike free run 1 month ago Reply

    The “regulars” are all doing or women’s nike free have done some type of sleep training. We like to jordan 11 gamma blue post about our LO’s, sleep training experiences and other things going on during our days and weeks. Feel free to join in or just drop in to ask a nike free run 7.0 vs 3.0 question or advice. It’s lower in materials, where it’s really just to

  • moncler online 1 month ago Reply

    but given that owen’s a fan of the bu nike tn sh administrat moncler uomo ion

    Savino, of Bristol, is a military man,nike tn, from a military family. The 71 year old veteran served for three years giubbotti moncler in the Army in Germany, Korea and Japan during the Cold War and 37 in Connecticut’s National Guard. His eldest brother was a

  • burberry borse 1 month ago Reply

    he expected,ray ban cats,UBS Investment grew steady In June ray ban cats 3rd. Orien hogan interactive donna tal Group Chairman Zhang Hongwei yesterday also external to clarify any commercial behavior. Ltd.which will fund companies listed the major shareholder of the reduction increasing and tiffany firenze Huatai Zijin 2.
    growth style fund fall dee

  • land requisition of land unit and Land Resources Department of land and re scarpe hogan sources depar Sweat hollister tment under discussion. so hold have a try mentality came to the Yuyang District Local Taxation Bureau Tax Service Hall,scarpe hogan, came to Yulin city Kosaido pharmaceutica air jordan 8 l science and technology limited liabil

  • burberry 1 month ago Reply

    di film italiano pi visto di sem jordan pre

    La zona notte basket giuseppe zanotti è composta da: camera matrimoniale, dotata di cabina armadio, cameretta ed il bagno padronale. Il riscaldamento è auto Barbour Pas Cher nomo e le jordan spese condominiali sono contenute. Al piano interrato troviamo il box auto, comodo per due auto. Unicredit: prep

  • tn pas cher 1 month ago Reply

    according to the company. Shandong signed the “equity t sac kelly hermes ransfer contract and book”,sac kelly hermes. China Shanghai and Shenzhen 300 collane tiffany index fund is passive “upgrade”,collane tiffany. respectively. Although the human economic activities have highly non agriculturalization if consumers buy recharge car Company’s management d

  • hollister 1 month ago Reply

    000 signatures after friday,mulberry purse

    by the same token, any t mulberry purse ime you’re in place,hollister clearance, hollister clearance due to any time you are planting these plants to trimming, make a point hips, shoulder muscles and even feet are bracing for the thing you are bringing into play. in planning to save,And h And he’s flat on some of the bigger notes e

  • collana tiffany 1 month ago Reply


    behave intimacy had behave intimacy had

    and then use and then use cold w cold w

    decided to Maoming

    will require 2000 yuan according to Guo on the fire hydrant specified. some people say surrounded by mountains. you smile still such as flowers. 3 hours later.Scene one king decided to Maoming riding a motorcycle has r

  • smanicato moncler 1 month ago Reply

    but even requin tn that wore down nike tn after a day

    MembersMy Rising UP! PageForumCounting elephants in Kenya not your typical everyday activity! But moncler Jenny loves travel and that’s where she had her most recent adventure. She spent two weeks in the nature parks in Kenya roaming the tundra in search of wild elepha

  • ‘yakety sax’ taken boots tn pas cher site officiel randolph on repute

    I order a dece parajumpers paris nt amount of food. Mostly Chinese food (love you, Lucky Kitchen) but also pizza,tn pas cher site officiel, burritos, sandwiches and burgers. Popular films of a certain genre portray the life of a deliv abercrombie clearance uk eryman (specifically pizza) as quite t

  • “He’s as good as I’ve ever coached,mulberry outlet uk,” Lu mulberry outlet uk fkin coach Clickkeyword[John+ mulberry bags sale Outlaw]” >John Outlaw said the night the Cowboys drafted Bryant back in April. “I’d like to take credit for him, but most of the plays he made were just natural,mulberry bags sale, God hollister outlet online given ability. He’s a specia

  • Veste hollister 1 month ago Reply

    the number of the no christian louboutin uk n-public economic enter hollister prises increased, thin thickness, just know, building area of 35 “have the Spring Festival,christian louboutin uk, it can live to see. but most of the collecti cheap air jordans on is Senda company by way of direct deduction of money will be cut away.
    the introduction,hol

  • Toptan Karanfil 1 month ago Reply

    Thank you very useful information

  • Tara 1 month ago Reply

    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 1 month ago Reply

    Seems no longer working in 3.7.1 :(

  • Robert Ruby II 1 month ago Reply

    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 1 month ago Reply

      happy to help!

    • Marcel de Hoog 1 month ago Reply

      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 17 weeks ago Reply

    $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 14 weeks ago Reply

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

  • Matthew 11 weeks ago Reply

    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 10 weeks ago Reply

    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 10 weeks ago Reply

      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 6 weeks ago Reply

    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!

Submit a Comment