Well that's quite the most boring post title I think I've ever come up with. But if you're here already, it means that you have at least some passing interest in either a) adding a 'primary category' to your posts, or 2) getting WordPress to autosave your post meta data properly. If this fascinating and insightful information isn't exactly what you're after, then I absolutely promise there are pictures of bare-naked ladies smothered in cheesecake right after the jump.

So here's an interesting (and quite irritating) fact which you may not have stumbled across before: when WordPress autosaves a post or a page, it wipes the values of your custom fields. You probably didn't notice, because like most sensible people you usually hit Update on a post before you leave the editor, and that safely saves any and all custom fields. But try this, just for the sake of hilarity: create a new post, add a custom field and fill in some data, then hit Publish. Now wait a few minutes for the post to autosave itself. Once it's done that, leave the post editor (click on Dashboard, for example), then go back to edit your post: your custom field values have magically disappeared.

This is because, for whatever reason, WordPress' autosave feature doesn't check for any non-standard $_POST values; so while the key of your custom field has already been saved, the value is wiped. Which sucks.

Having explained all of that nonsense, I'm now going to jump back to the first point of this post: adding a primary category. Categories are handy, fun, and helpful around the house. However, in certain circumstances you might want one particular category to stand out above the rest. For example, this post is categorised in both PHP and WordPress, but its main focus is WordPress, which is why that lovely little grey badge is displaying at the top of the page. Oh, what? You haven't noticed that awesome feature? Then jump to the top! See what I mean?

Rather than use a clunky regular custom field we're going to make things nice and elegant by adding a dropdown menu to the post screen. To do that, we need to hook into the admin_init hook in your functions.php file:

<?php
add_action('admin_init', 'primecat_init');

function primecat_init() {
 add_meta_box("category-meta", "Primary Category", "primaryCategory", "post", "side", "low");
}

function primaryCategory() {
 global $post;
 
 $catKey = "primary_cat";
 $catSlug = get_post_meta($post->ID, $catKey, true);
 
 $catTerms = get_term_by('slug', $catSlug, 'category');
 $categoryID = $catTerms->term_id;
 
 ?>
 <label>Category:</label>
 <?php wp_dropdown_categories('show_option_none=Select category&name=primary_cat&hide_empty=0&selected=' . $categoryID);
}
?>

By hooking into admin_init we're adding a new section to the post/page editor screen with add_meta_box; giving it an ID of category_meta, a title of Primary Category, with the callback set to the primaryCategory function. The other three values are the post type ("post"); the 'context', or part of the page you want it to appear ("side"); and its display priority ("low").

The primaryCategory function then pulls out the list of categories for displaying in the dropdown. This function appears slightly overcomplicated because I want to save the meta value as the category slug, but for this dropdown menu I want to pull out the category ID.

Then we use a standard wp_dropdown_categories function to display the list of categories, making sure to display empty ones too ('&hide_empty=0') and, if we're editing a post that's already been published, we want it to display the primary category that's already been set ('&selected=' . $categoryID').

That will now show a neat little Primary Category dropdown on the post editor screen, but we need to save that data.

<?php
add_action('save_post', 'save_cat_details');

function save_cat_details($post_id) {
 global $post;
 
 $catID = $_POST["primary_cat"];
 
 if(isset($_POST["primary_cat"]) && $catID != "") {
  $catTerms = get_term_by('term_id', $catID, 'category');
  
  $catName = $catTerms->slug;
  
  update_post_meta($post->ID, "primary_cat", $catName);
 }
}
?>

This hooks into the save_post action, which should be fired any time a post is updated, including an autosave. But, just to be sure, we're going to check if the $_POST data we're expecting is being sent through with the condition if(isset($_POST["primary_cat"]) &amp;&amp; $catID != "").

After that we simply pull out the category slug (since that's what I want to save, because I'm being awkward) and use update_post_meta to store what is essentially a custom field value in the normal way.

You should be able to use the function above to ensure autosave will store any of your custom field values, provided you know what those meta keys are going to be and you can check for them in $_POST.

Finally, I suppose you'll want to pull your primary category out and use it in your theme? Simple as:

<?php
  $catKey = "primary_cat";
  $catSlug = get_post_meta($post->ID, $catKey, true);

  echo "This post was brought to you by the number " . rand(1, 12) . " and the category '" . $catSlug . "'.";

  $catTerms = get_term_by('slug', $catSlug, 'category');
  print_r($catTerms);
?>

Just for fun I threw the $catTerms variable in there which will contain your category object, which you can then use for all the fun and frolicks of the WordPress fair.


  • http://twitter.com/stompweb Steven Jones

    I don’t think you’d be having the problem in the first place if you were using..

    // do not save if this is an auto save routine
    if (defined(‘DOING_AUTOSAVE’) && DOING_AUTOSAVE) return $post_id;

    • http://blog.gaijindesign.com/ Lawrie Malen

      I initially considered escaping the whole autosave routine, but that seems more like avoiding the problem than trying to fix it. I’ve had a couple of dramatic moments where, after spending half an hour drafting a blog post, either the browser crashed or I accidentally closed 14 tabs at once and autosave (literally) saved my (figurative) bacon.

  • Sara

    Thanks so much for this great and informative post! I would love, love, love to see screenshots of this in action in the editor … any chance of that?

    Thanks again.

  • http://twitter.com/Glark David T. Cole

    Hi, does this still work with 3.4.1? Nothing new appears in the post edit page (tested with vanilla TwentyEleven to double check it wasn’t a theme/plugin issue).

    • http://twitter.com/Glark David T. Cole

      Found the problem. You have “primecat_init” in the second line and “primcat_init” on the next.

      • http://blog.gaijindesign.com/ Lawrie Malen

        Thanks David, well spotted. I’ve updated the post accordingly.

  • Jacob

    Hi, How can I make the posts primary category link display bold in a sidebar menu?
    Thanks

  • Sara

    Thank you again! I have another question to wrap your super-clever programming brain around: I am working on a site which has a “newspaper” type of front page, with many sections on that page that lists the most recent posts in each category.

    I would instead like to make each of these category post-list sections of the page show a list of the most recent posts only where that category is the PRIMARY category for the post.

    Does that make sense? I might have a category “England” and a category “Theater Musings” on my front page. A post might be assigned to both of those categories. But I want it to show up on the front page ONLY in the “Theater Musings” category posts list on the page, NOT in the England category posts list section.

    Thanks for any brilliant thoughts on this!

  • Alexander

    There is no $_POST["primary_cat"] when autosave. It cannot be saved.

    Besides if you want to restore one of your revisions or autosaved record there is no history about “primary_cat”. So forget about an autosave.

  • Manish Mukherjee

    This is great! Thanks. I made one addition. In your original code, the primary category doesn’t get added to the regular list of categories. For my use case I want to make sure that if a user selects a primary category, that category still gets added to the main list of categories.

    So I added:

    if (isset($_POST["primary_cat"]) && $catID != “”) {
         $catTerms = get_term_by(‘term_id’, $catID, ‘category’);
         $catName = $catTerms->slug;
         update_post_meta($post->ID, “primary_cat”, $catName);
         // My additions below.
         $current_cats = wp_get_post_categories( $post->ID );
         array_push($current_cats, $catID);
         wp_set_post_categories($post->ID, array_unique($current_cats));
    }