WordPress Development Stack Exchange is a question and answer site for WordPress developers and administrators. Join them; it only takes a minute:

Sign up
Here's how it works:
  1. Anybody can ask a question
  2. Anybody can answer
  3. The best answers are voted up and rise to the top

So on the add post screen in wordpress there is the category checkbox div. Before you select a category for your post it shows the correct hierarchical structure as defined in the category admin screen. After selecting a category and saving the post it will put your selected category at the top of the list instead of keeping the structured layout. Is there a way to stop it from doing this?

share|improve this question
Is this what your are looking for? [wordpress.org/extend/plugins/my-category-order/] – josh green Aug 15 '12 at 15:23
up vote 0 down vote accepted


Use the add_meta_boxes_post to add a new category metabox that preserves the structure and while you're at it, remove the default one, like this:

add_action( 'add_meta_boxes_post', 'fix_hierarchical_post_categories_metaboxes', 10, 1 );

And here's the callback:

 * This include provides an alternative post category metabox that sets the checked_ontop argument of wp_terms_checklist to false.
 * This prevents the re-ordering of selected category items to the top of the list, instead preserving their hierarchical
 * order.

function fix_hierarchical_post_categories_metaboxes( $post ){
    global $wp_meta_boxes;
    foreach ( $wp_meta_boxes as &$post_metaboxes ){
        foreach ( $post_metaboxes as &$position_metaboxes ){
            foreach ( $position_metaboxes as &$priority_metaboxes ){
                foreach ( $priority_metaboxes as &$metabox ){
                    if ( $metabox['callback'] == 'post_categories_meta_box' ){
                        $metabox['callback'] = 'hierarchical_post_categories_meta_box';

function hierarchical_post_categories_meta_box( $post, $box ) {
    $defaults = array('taxonomy' => 'category');
    if ( !isset($box['args']) || !is_array($box['args']) )
        $args = array();
        $args = $box['args'];
    extract( wp_parse_args($args, $defaults), EXTR_SKIP );
    $tax = get_taxonomy($taxonomy);

    <div id="taxonomy-<?php echo $taxonomy; ?>" class="categorydiv">
        <ul id="<?php echo $taxonomy; ?>-tabs" class="category-tabs">
            <li class="tabs"><a href="#<?php echo $taxonomy; ?>-all" tabindex="3"><?php echo $tax->labels->all_items; ?></a></li>
            <li class="hide-if-no-js"><a href="#<?php echo $taxonomy; ?>-pop" tabindex="3"><?php _e( 'Most Used' ); ?></a></li>

        <div id="<?php echo $taxonomy; ?>-pop" class="tabs-panel" style="display: none;">
            <ul id="<?php echo $taxonomy; ?>checklist-pop" class="categorychecklist form-no-clear" >
                <?php $popular_ids = wp_popular_terms_checklist($taxonomy); ?>

        <div id="<?php echo $taxonomy; ?>-all" class="tabs-panel">
            $name = ( $taxonomy == 'category' ) ? 'post_category' : 'tax_input[' . $taxonomy . ']';
            echo "<input type='hidden' name='{$name}[]' value='0' />"; // Allows for an empty term set to be sent. 0 is an invalid Term ID and will be ignored by empty() checks.
            <ul id="<?php echo $taxonomy; ?>checklist" class="list:<?php echo $taxonomy?> categorychecklist form-no-clear">
                <?php wp_terms_checklist($post->ID, array( 'taxonomy' => $taxonomy, 'popular_cats' => $popular_ids, 'checked_ontop' => false ) ) ?>
    <?php if ( current_user_can($tax->cap->edit_terms) ) : ?>
            <div id="<?php echo $taxonomy; ?>-adder" class="wp-hidden-children">
                    <a id="<?php echo $taxonomy; ?>-add-toggle" href="#<?php echo $taxonomy; ?>-add" class="hide-if-no-js" tabindex="3">
                            /* translators: %s: add new taxonomy label */
                            printf( __( '+ %s' ), $tax->labels->add_new_item );
                <p id="<?php echo $taxonomy; ?>-add" class="category-add wp-hidden-child">
                    <label class="screen-reader-text" for="new<?php echo $taxonomy; ?>"><?php echo $tax->labels->add_new_item; ?></label>
                    <input type="text" name="new<?php echo $taxonomy; ?>" id="new<?php echo $taxonomy; ?>" class="form-required form-input-tip" value="<?php echo esc_attr( $tax->labels->new_item_name ); ?>" tabindex="3" aria-required="true"/>
                    <label class="screen-reader-text" for="new<?php echo $taxonomy; ?>_parent">
                        <?php echo $tax->labels->parent_item_colon; ?>
                    <?php wp_dropdown_categories( array( 'taxonomy' => $taxonomy, 'hide_empty' => 0, 'name' => 'new'.$taxonomy.'_parent', 'orderby' => 'name', 'hierarchical' => 1, 'show_option_none' => '&mdash; ' . $tax->labels->parent_item . ' &mdash;', 'tab_index' => 3 ) ); ?>
                    <input type="button" id="<?php echo $taxonomy; ?>-add-submit" class="add:<?php echo $taxonomy ?>checklist:<?php echo $taxonomy ?>-add button category-add-sumbit" value="<?php echo esc_attr( $tax->labels->add_new_item ); ?>" tabindex="3" />
                    <?php wp_nonce_field( 'add-'.$taxonomy, '_ajax_nonce-add-'.$taxonomy, false ); ?>
                    <span id="<?php echo $taxonomy; ?>-ajax-response"></span>
        <?php endif; ?>

The first function is responsible for swapping the metabox callback on the default post_categories_meta_box for our new one.

The second function is basically a copy-and-paste of the original post_categories_meta_box callback, where I've modified the part that puts the selected categories on top.

Seems like a lot of work for something so basic, eh?

share|improve this answer
thanks you. this worked perfectly – Kyle Williams Aug 15 '12 at 17:43

The 'wp_terms_checklist_args' filter might not have been around back in August 2012 but now there's a much easier way of making sure checked terms stay put.

This filter is called right inside the wp_terms_checklist() function and let's you filter the arguments used to create the checklist:

add_filter( 'wp_terms_checklist_args', 'my_website_wp_terms_checklist_args', 1, 2 );
function my_website_wp_terms_checklist_args( $args, $post_id ) {

   $args[ 'checked_ontop' ] = false;

   return $args;

share|improve this answer
Thx Rachel - was just what I needed :) – chuck reynolds Aug 19 '14 at 1:44
It works perfect! – Roman Jun 10 at 21:35

Your Answer


By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.