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?



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?


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;

  • Thx Rachel - was just what I needed :) Aug 19 '14 at 1:44
  • It works perfect!
    – Roman
    Jun 10 '16 at 21:35

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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