Saving custom fields in quick or bulk edit mode in WordPress

One of the most cool features when managing WordPress posts, is the ability to batch edit multiple posts at once or quickly edit one post to perform a simple task such as adding a tag. Learn how to extend WordPress’ Quick Edit and Bulk Edit to retrieve and save the value of your custom fields.

Overview

Let’s take a look at how it will work:

This code has been tested using TwentyEleven, the default theme of WordPress 3.2 but it should work consistently on other themes since it doesn’t uses anything from the theme. Fire up your IDE and open the functions.php file of TwentyEleven and after the first block of comments, add the following code:

<?php
if ( is_admin() ) {
    require_once( TEMPLATEPATH . '/ilc-admin.php' );
}

We’re going to place all of our code in this external file (that you can download at the bottom of the this article). We will be relying on jQuery to retrieve and save the custom field values using AJAX queries.

Creating a custom post type

To spice things up and show how adaptable this code is, we’re going to create a custom post type named Events to use our code:

add_action('init', 'ilc_init');

function ilc_init() {
    register_post_type(
        'event',
        array(
            'labels' => [
                'name' => 'Events',
                'singular_name' => 'Event',
            ],
            'public' => true,
            'menu_position' => 5,
            'supports' => ['title', 'editor', 'custom-fields']
        )
    );
}

Creating custom columns

First of all, we’re going to add some columns in the list view of your events to see the value of our custom field, before and after saving it. It’s a good way to quickly see the values of your custom fields.

In order to simplify things, we’re simply assuming that the custom field exists and has a value. You can add a meta box in the post writing screen with a super duper cool calendar to select the date if you want to or you can add a custom field in the old way. We don’t even need it to be there because it will be created if it doesn’t exists.

<?php
add_filter( 'manage_event_posts_columns', 'ilc_cpt_columns' );
add_action( 'manage_event_posts_custom_column', 'ilc_cpt_custom_column', 10, 2 );

function ilc_cpt_columns($defaults) {
  $defaults['event'] = 'Event Date';
  return $defaults;
}

function ilc_cpt_custom_column($column_name, $post_id) {
  $eventdate = get_post_meta($post_id, 'eventdate', true);
  if( $eventdate ) {
    echo '<em>' . $eventdate . '</em>';
  }
  else echo '<em>No date set.</em>';
}

If you don’t want to display additional columns of info (why?), add only the filter disregarding the action and change the function referenced by the filter:

function ilc_cpt_columns( $defaults ) {
  $defaults['event'] = '';
  return $defaults;
}

and now no column will be shown.

Displaying and saving custom fields values in quick edit mode


Let’s begin saving our custom fields by tackling quick edit saving. The following code will create an input element to display the value of the custom field.

<?php
add_action('quick_edit_custom_box', 'ilc_quickedit_show', 10, 2);

function ilc_quickedit_show( $col, $type ) {
    if ( $type != 'event' ) {
        return;
    }
    ?>
    <fieldset class="inline-edit-col-left">
        <div class="inline-edit-col">
            <div class="inline-edit-group">
                <label style="font: italic 12px Georgia, serif;" for="eventdate">Event Date</label>
                <span class="input-text-wrap">
                    <input id="eventdate" type="text" name="eventdate" value="" size="10" />
                </span>
                <input type="hidden" name="is_quickedit" value="true" />
            </div>
        </div>
    </fieldset>
<?php
}

Notice that the post type is checked to display the field only for event post types and to return if it isn’t. Squint your eyes and you’ll see a hidden field named is_quickedit. Remember it and we’ll come back to it after the next code snippet.

Retrieving and displaying the custom field value

Dissapointingly enough, the last code displayed an empty field and that’s because we need to retrieve the value first. Let’s do it:

<?php
add_action('admin_head-edit.php', 'ilc_quickedit_get');
function ilc_quickedit_get() {
    ?>
    <script type="text/javascript">
        jQuery(document).ready(function() {
            jQuery("a.editinline").live("click", function() {
                var ilc_qe_id = inlineEditPost.getId(this);
                jQuery.post(
                    "<?php echo YOUR_THEME_URI ?>/ilc-admin.php", {
                        ilc_post_id: ilc_qe_id,
                        ilc_mode: 'ajaxget'
                    },
                    function(data) {
                        jQuery('#eventdate').val(data);
                    }
                );
            });
            var ilc_post_ids = [];
            jQuery('.ilc-updated').hide();
            jQuery('#doaction, #doaction2').click(function(e) {
                var n = jQuery(this).attr('id').substr(2);
                if (jQuery('select[name="' + n + '"]').val() == 'edit') {
                    e.preventDefault();
                    jQuery('tbody th.check-column input[type="checkbox"]:checked').each(function(i) {
                        ilc_post_ids.push(jQuery(this).val());
                    });
                } else if (jQuery('form#posts-filter tr.inline-editor').length > 0) {
                    t.revert();
                }
            });

        });
    </script>
    <?php 
}

Things are getting messier with this last JavaScript code, but as you can see, we’re using AJAX to obtain the value and the url queried is this very same file (that’s why we’re using a separate file to keep things clean). To process our AJAX query, add the following at the top of the current file:

if(isset($_POST['ilc_mode'])){
    require_once('../../../wp-blog-header.php');
    if($_POST['ilc_mode'] == 'ajaxget'){
        $ilc_post_id = $_POST['ilc_post_id'];
        echo get_post_meta($ilc_post_id, 'eventdate', true);
    }
    elseif($_POST['ilc_mode'] == 'ajaxsave'){
        $ilc_ids = explode(" ", $_POST['ilc_ids']);
        foreach ( $ilc_ids as $ilc_post_id)
            update_post_meta($ilc_post_id, 'eventdate', $_POST['ilc_val']);
    }
    return;
}

The variable ilc_mode sent in the AJAX query will have two values: ajaxget or ajaxsave. In this way, we know when the query is used to get the custom field value or set it to a different value. If the mode is ajaxget, we get the post ID and retrieve the post meta using it. The ajaxsave mode is only used by the bulk edit mode, so you can remove it if you’re not going to code it.

Saving the custom field value

The last piece of code we need is the one that actually saves our custom fields values:

add_action( 'edit_post', 'ilc_quickedit_save', 10, 2 );
function ilc_quickedit_save( $post_id, $post ) {
  if( $post->post_type != 'event' ) {
    return;
  }
  if ( isset( $_POST['is_quickedit'] ) ) {
        update_post_meta($post_id, 'eventdate', $_POST['eventdate']);
  }
}

Again, we save the value only if the post type is event, but in this case, we also check the is_quickedit variable mentioned before: if it is set, we know that the user is in the events list view saving the value using quick edit. If it is not set, the user is in the post writing interface, so trying to save the custom field value using the quick edit variable eventdate will fail, since it’s not in the $_POST array, thus resulting in a blank field.

After all your hard work, you’re now saving the custom field value using the quick edit mode, yay! pat yourself in the back, get a coffee and come back for the final round, the bulk edit mode.

Saving values in bulk edit mode

This is when everything gets ugly and let me explain you why I’ve separated the quick edit from the bulk edit. First reason is that even though they share a lot of code, most of the people won’t ever use it. The second reason is that it requires a nasty hack: an additional saving button. Now is the time where you make that “suspicious” face. If you’ve found some other way to do it, please, go ahead and share it the comments.

Creating the input element

Like almost everything in the web, the code for the bulk edit begins with some good old HTML:

<?php
add_action('bulk_edit_custom_box', 'ilc_quickedit_bulk', 10, 2);
function ilc_quickedit_bulk($col, $type) {
    if ($type != 'event') {
        return;
    }
    ?>
    <fieldset class="inline-edit-col-left">
        <div class="inline-edit-col">
            <div class="inline-edit-group"><label style="font: italic 12px Georgia, serif;" for="eventdate">Event Date</label>
                <span class="input-text-wrap">
                    <input id="eventdatebulk" type="text" name="eventdatebulk" value="" size="10" />
                </span></div>
        </div>
        Event date updated

        <a class="ilc-bulk-update button-secondary" href="#">Update event date</a>
    </fieldset>
    <?php
}

Updating the custom field value

Pretty similar to the HTML for quick edit, but we don’t need the additional hidden field since the custom field value won’t be saved using the edit_post hook: we will use AJAX and the ajaxsave mode to update the value. This is the same function used for the quick edit mode but has additional code to handle the bulk editing mode:

<?php
add_action( 'admin_head-edit.php', 'ilc_quickedit_get' );
function ilc_quickedit_get() {
?>
    <script type="text/javascript">
        jQuery(document).ready(function() {
            jQuery("a.editinline").live("click", function() {
                var ilc_qe_id = inlineEditPost.getId(this);
                jQuery.post(
                    "<?php echo YOUR_THEME_URI ?>/ilc-admin.php", {
                        ilc_post_id: ilc_qe_id,
                        ilc_mode: "ajaxget"
                    },
                    function(data) {
                        jQuery("#eventdate").val(data);
                    }
                );
            });

            var ilc_post_ids = [];
            var ilc_bulk_value = '';
            var ilc_post_ids_flat = '';

            jQuery(".ilc-updated").hide();

            jQuery('#doaction, #doaction2').click(function(e) {
                var n = jQuery(this).attr('id').substr(2);
                if (jQuery('select[name="' + n + '"]').val() == 'edit') {
                    e.preventDefault();
                    jQuery('tbody th.check-column input[type="checkbox"]:checked').each(function(i) {
                        ilc_post_ids.push(jQuery(this).val());
                    });

                } else if (jQuery('form#posts-filter tr.inline-editor').length > 0) {
                    t.revert();
                }
            });

            jQuery(".ilc-bulk-update").live("click", function() {
                ilc_bulk_value = jQuery('input[name="eventdatebulk"]').val();
                for (var ilc_i = 0; ilc_i < ilc_post_ids.length; ilc_i++) {
                    ilc_post_ids_flat = ilc_post_ids_flat + " " + ilc_post_ids[ilc_i];
                }
                jQuery.post(
                    "<?php echo YOUR_THEME_URI ?>/ilc-admin.php", {
                        ilc_ids: ilc_post_ids_flat,
                        ilc_mode: "ajaxsave",
                        ilc_val: ilc_bulk_value
                    },
                    function(data) {
                        jQuery(".ilc-updated").fadeIn(300).delay(800).fadeOut(300);
                        jQuery("input#bulk-edit").click();
                    }
                );
            });


        });
    </script>
<?php
}

Now, when you select two or more events and enable bulk editing mode, you’ll have the additional field and the additional button to save it. After saving the value, we will trigger the click event of the main button to save the other values you might have changed. Just like common Bulk Editing, page will be reloaded and your changes will be applied.

Download the code

Here you have the sample code, place it next to your functions.php file in your WordPress theme and type the first code snippet shown in this tutorial. Have fun playing with it!

Download Quick & Bulk Edit for Custom Fields in WordPress

33 thoughts on “Saving custom fields in quick or bulk edit mode in WordPress”

  1. Hello there,
    is there any chance to get that working with my regular blog posts? I do not use custom-post-types.
    Would be great to find a solution to quick edit the values of the custom-fields of my posts.
    Many thanks and sorry for my english.
    Sascha

    1. Sascha, you would just have to remove the code that’s tailored for CPT ‘event’. For example:
      [php]
      if( $post->post_type != ‘event’ ) return;
      [/php]
      and other ocurrences like that one.
      In addition, manage_event_posts_columns would be just manage_event_posts_columns.

  2. Hey Elio, thanks for the quick replay 😉
    The thing is that i am not that familiar with php.
    Would you may help me to get that working?
    Maybe you are able to email me.

    For example …
    I have 3 Categories.

    1.category-one
    2.categorie-two
    3.categorie-three

    And each of my posts has 4 custom fields.
    1. size
    2. color
    3. name
    4. age

    It would be so cool if i am able to change the values over the quick edit button.

    Sascha

  3. Thanks for this excellent work! I can’t tell you how much time this has saved me and will save me in the future however I do have one problem I can’t seem to pinpoint at the moment.
    My custom field updates ok and appears in the column as it should but if I click quick-edit again the custom field value entered the first time is missing and then of course lost if I click update to save the post.

    Any ideas would be most appreciated.

  4. Hey Elio, can you please help me to get that done with the custom fields i use for my regular posts? I have played arround with the code without any luck. As i wrote in my last comment it would be awesome to get that working. Would be awesome if you are able to point me in the right place.

  5. Great tutorial thank you for sharing this. Am still having a little difficulty hoping you can help me out – for some reason its printing out the value of my custom field twice, its also printing the input box in side quick edit twice. Not really sure why but it seems that the entire code is getting run twice. Any idea why?

  6. been working on this all day and i no longer think you need the is_quickedit field. you can test for $_POST[‘action’] == ‘inline-save’ instead.

  7. This may be a bit off-topic , but I am a shameless guy and I’ll just ask here with all my shamelessness,

    Do you have any ideas on how to get the custom field values via ajax in the front end ?

  8. Yeah, this tutorial is great, but I need to know how to populate the custom field in the quick edit box with the current value if any. I tried doing a ID, ‘custom_field’, true); but that’s not working. Thanks!

  9. Alright, I figured out why the current value is not being passed to the quick edit field.

    My simple solution, but its temporary until I find a better way, is to copy the ilc-admin.php file to your
    wp-admin folder

    Open up that file in your text editor and change the following line:
    require_once(‘../../../wp-blog-header.php’);
    TO:
    require_once(‘../wp-blog-header.php’);

    Also, open up the ilc-admin.php file that is located in your themes directory and edit these lines
    define(‘ILCTHEMEURI’, get_template_directory_uri());

    to
    //define(‘ILCTHEMEURI’, get_template_directory_uri());

    Just comment it out

    Also, at the end of the file where the javascript (jquery) is edit this line:
    “/ilc-admin.php”
    TO:
    “ilc-admin.php”

    there is one more identical one just like it below that one that you have to change.

    Once I did all of this, I was able to see the values when I clicked on Quick Edit.

    Hope this helps!

  10. @Max : would you be so kind of saying what’s the good way??

    @Elio : i believe you mean manage_posts_columns

    I was having a duplicate field issue and solved it adding a second condition when testing for the post_type.
    if( $type != 'event' || $col !='event' ) return;

  11. Hi thanks for such an awesome tutorial but sadly I am unable to use it successfully yet. My custom field name is ”price“ can you please tell me how to make it work?

    Thanks

  12. @Olie

    You can use the ajax server from the plugin directory provided that you add this line right after the required_once line:
    `header(‘HTTP/1.1 200 OK’);`

    Otherwise the template_redirect will throw a 404 header instead, even if the content is right after that.

  13. Thanks Richard, I will try it out and see. It’s definitely a great tool for quick edits! I am sure there are plenty ways around the error, I just needed something quick. I will install fresh and try your suggested technique. Can you explain what it does though?

    Thanks!

  14. when you run wp-blog-header.php it executes a template redirect that will setup a 404 header to the response (because the original php file cannot be recognized as a theme file). Next line will repair that setting the seader back to 200.

    This way jQuery post function will not throw an error and will continue execution as expexted

  15. I have been trying to figure out how to add the description field to quick edit function for tags and categories. Is there any advice or tips you could give me for that?

  16. Thanks for your Tutorial. It helped me a lot.

    Thinking about the solution for the “bulk edit” I found out that there also is a PHP solution for saving your custom bulk values that works without additional AJAX.

    You can e.g. in your functions.php add following lines:
    if(isset($_REQUEST[‘bulk_edit’])) {
    foreach($_REQUEST[“post”] as $post_id) {
    // Do what you have to do with the selected posts
    }
    }

    The code after the if-statement will be called when bulk fields are supposed to be saved before the page is reloaded again. You can access all submitted variables at this point via $_GET normally, but $_REQUEST considers both, $_GET and $_POST.

    Hope that helps some people who are looking for a solution to save custom bulk values.

  17. Hey nice tutorial. It’s acctualiy amazing that there is not yet a plugin to add that functionality. Thanks for the explanation.

    How is it possible to add more collumns and fields? You’re using:
    $defaults[‘event’] = ‘Event Date’;
    How can I add more collumns to the selected CPT?

    Thnaks a Lot,

  18. Just thought i’d drop a comment and say thanks for the guide, there’s alot of action hooks I weren’t aware of that you’ve brought to my attention. Really useful, cheers.

  19. Nice tutorial!

    But please notice that the method for saving custom fields in the “Quick Edit Mode” works also for the “Bulk Edit Mode”.

    In other words:
    add_action(‘quick_edit_custom_box’, ‘ilc_quickedit_show’, 10, 2); This also works for the bulk edit save action.

    I’m using the latest WP version (5.6).

    I don’t know if it is related to version.

Leave a Reply