Filtering WordPress Categories Using an Undocumented Hook

In this article, we will briefly cover WordPress hooks and use an undocumented one in order to manipulate category listings.

After some further digging, while it is not listed on the WordPress filter list, it appears that this “undocumented hook” is mentioned on the get_terms() function reference. So, we’ll call this a somewhat undocumented hook.

Anybody who knows me knows that I love WordPress, if perhaps not more than any man should love a collection of code. I believe there are few projects for which it should not at least be considered.

Recently, I have been adapting WordPress to a large project with multiple authors/contributors. Each user would have a set of capabilities and restrictions. The most important of these was limiting users to certain categories. If this were my only need, the very useful Role Scoper plug-in would have worked wonderfully. However, there were a number of other requirements that Role Scoper did not meet, and the use of multiple plug-ins would likely confuse my not quite tech savvy clients. Therefore, I was forced to create my own solution that included the required category restrictions.

WordPress generally makes it very easy to filter and adjust any data/output before it is sent to the end-user. This is done with Filters. Essentially, you are able to hook in to a number of pre-defined functions and pass the output to your own custom function. The code for achieving this is done so in the following format:

<?php add_filter( $tag, $function_to_add, $priority, $accepted_args ); ?>

Most important here are $tag, the function you want to hook into, and $function_to_add, your custom function that will filter the data provided. The other arguments are optional but more information can be found in the WordPress codex.

This code can be placed in the file for your custom plug-in or in the functions.php for your theme. For example, if you wanted the name of your blog to be red every time it appears in a post you could use the following code.

add_filter('the_content', 'styleBlogName');
function styleBlogName($content) {
	$blogTitle = get_bloginfo('name');
	$content = str_replace($blogTitle, '<span style="color: Red;">' . $blogTitle . '</span>', $content);
	return $content;

Here, we’ve added a filter to that will be called every time a theme calls the_content(), which outputs the content of a post. WordPress then passes the current content of the post to ourstyleBlogName() function. We use str_replace() to replace any instances of our blog title with a new version wrapped in a span we can style. Lastly, you have to remember to return the new value of $content, or WordPress will assume the post is blank. Pretty simple.

Every hook that we can attach to has different arguments. You can find a semi-complete list of these hooks in the codex.

WordPress is a huge project with an expansive code base. Unfortunately, there are a number of things that are not yet covered in the available documentation. And some of the things that are covered have been deprecated. During a sleepless night, while doing research on the project I described above, I was digging through the WordPress code and stumbled upon an undocumented filter hook, get_terms.

get_terms hooks into the function that retrieves all information about categories and is called in a large number of places throughout the WordPress code. For example, it is called whenever a theme calls the wp_list_categories() function, when listing the categories in the sidebar of the Edit Post screen in the admin panel, and many other places.

When hooking into this filter, WordPress will pass your callback function an array of objects, each object containing information about an individual category. To get a good idea of the data structure, insert the following code into the functions.php file of your current theme, visit a page of your blog that lists the categories, and view the source code.

add_filter('get_terms', 'restrict_categories');
function restrict_categories($categories) {	
	echo "<!-- \n";
	echo "//-->\n";
	return $categories;

Now that we understand the data structure, we can proceed to process it some way. There are limitless possibilities, so I’m going to keep my example simple and leave you to your own imagination. Imagine that you have a WordPress installation with one Admin and multiple Authors. You have a category called “Site News” that you only want the Admin to be able to post to. Therefore you don’t want the Authors to even be able to see it in the category checklist when composing a new post. Here is how you would achieve that by hooking intoget_terms.

// add_filter('get_terms', 'restrict_categories');
function restrict_categories($categories) {	

	// If we are in the new/edit post page and not an admin, then restrict the categories
	$onPostPage = (strpos($_SERVER['PHP_SELF'], 'post.php') || strpos($_SERVER['PHP_SELF'], 'post-new.php'));	
	if (is_admin() && $onPostPage && !current_user_can('level_10')) {
		$size = count($categories);
		for ($i = 0; $i < $size; $i++) {			
			if ($categories[$i]->slug != 'site_news')

	return $categories;

We begin by checking that we are in the admin panel (so the ‘Site News’ category shows up on the main site), on the Add Post or Edit Post page, and are not an admin. If all these conditions are met, we iterate through the array of objects and check each category slug. If the slug is equal to ‘site_news’, we simply remove the object from the array. Return our new$categories variable and we’re done. You’ve now done something that many WordPress theme and plug-in developers aren’t even aware of. Way to go you.

WordPress is ridiculously powerful and makes developing websites much simpler than was previously possible. While it has evolved into something far beyond your typical blog software, sometimes you have to get your hands dirty and dig through the code to truly unleash its power. While sites like Adam Brown’s hook reference have attempted to further document all the hooks available to theme/plug-in developers, there is still a lot to discover.

Good luck and happy hunting!