Multisite – Protect categories from deletion?

All we need is an easy explanation of the problem, so here it is.

UPDATE: For anyone following this thread, the plugin solution does work, but seems to have a problem on my multisite format. I have now tested on 3 individual installs and on 3 multisites. On the multisites, the red “undeletable” marker appears next to the categories in the array, but they can get deleted. This may well be down to a problem with my particular install, as it works properly with newly created subsites.

Original Post:
Working on a multisite & would like a way to protect a couple of default categories from being deleted by site owners.

I’m already using the “members” plugin to define admin roles, but the only option there is “manage categories”, and if I disable that capability they can’t delete categories, but they can’t create new ones either.

Any way to allow new categories but not to delete a few special ones??

How to solve :

I know you bored from this bug, So we are here to help you! Take a deep breath and look at the explanation of your problem. We have many solutions to this problem, But we recommend you to use the first method because it is tested & true method that will 100% work for you.

Method 1

Extending @Roman’s answer.

The following was developed and tested in a Multisite environment – local and live WP installs.

Looking at the source of wp_delete_term, there are some hooks that are triggered when the function is called.

I’m not sure if this is the best way of doing this, but it works.

add_action( 'delete_term_taxonomy', 'wpse_70758_del_tax', 10, 1 );

function wpse_70758_del_tax( $tt_id )
    $undeletable = array( 'undeletable', 'uncategorized', 'other-cat' );
    $term = get_term_by( 'id', $tt_id, 'category' );

    if( in_array( $term->slug, $undeletable ) ) 
        wp_die( 'cant delete' );

So, when trying to delete the “Undeletable” category (using the Quick Edit menu), it produces:

can't delete category

As the deletion happens via Ajax, we are breaking its execution and WP dumps that error message (modifying that text deserves a separate Question).

If we use the “Bulk actions“, that wp_die() is printed in the screen.

But that’s not all. Before the actual deletion (action hook delete_term_taxonomy), the child categories of the one we are blocking are “unparented”, line #1772:

// Update children to point to new parent

There’s another hook where this action happens and we can insert an early break to prevent the “unparenting”:

add_action( 'edit_term_taxonomies', 'wpse_70758_del_child_tax', 10, 1 );

function wpse_70758_del_child_tax( $arr_ids )
    $undeletable = array( 'undeletable', 'uncategorized', 'other-cat' );
    foreach( $arr_ids as $id )
        $term   = get_term_by( 'id', $id, 'category' );
        $parent = get_term_by( 'id', $term->parent, 'category' );

        if( in_array( $parent->slug, $undeletable ) ) 
            wp_die( 'cant delete' );        

Use this inside a Must Use Plugin and it will be automatically active in the network.

The snapshot above has a custom ID column in the category listing. That’s done with the following code (an here with an extra column marking the “undeletable” categories with a big red bullet):

add_filter( 'manage_edit-category_columns', 'wpse_70758_cat_edit_columns' );
add_filter( 'manage_category_custom_column', 'wpse_70758_cat_custom_columns', 10, 3 );

function wpse_70758_cat_edit_columns( $columns )
    $columns['tt_id'] = 'ID';   
    $columns['undeletable'] = 'Undeletable';   
    return $columns;

function wpse_70758_cat_custom_columns( $value, $name, $tt_id )
    if( 'tt_id' == $name ) 
        echo $tt_id;

    $term = get_term_by( 'id', $tt_id, 'category' );
    $undeletable = array( 'undeletable', 'uncategorized', 'other-cat' );

    if( 'undeletable' == $name && in_array( $term->slug, $undeletable ) )
        echo '<span style="color:#f00;font-size:5em;line-height:.5em">&#149;</span>';

Full working plugin:

Method 2

Maybee you can hook to wp_delete_term function with action “delete_term” (see info at: and if such particular term is deleted, you can insert it once again…

this is not so clean solution and users would look bit surprised that term remains still after they clicked “delete link” but it can do the job, actually i think it is only solution…

Note: Use and implement method 1 because this method fully tested our system.
Thank you 🙂

All methods was sourced from or, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0

Leave a Reply