Add a ClassicPress or WordPress CPT admin page under a custom menu entry

Published 17 May 2020 in WordPress, ClassicPress, Plugins by ZigPress

Let’s say you are creating a new ClassicPress or WordPress plugin. It’s quite a big plugin and you’ve decided it should have its own top level entry in the admin menu.

Now let’s say that your plugin sets up a custom post type (CPT) and a custom taxonomy. However, you want the admin page for the CPT to be a submenu entry of your plugin’s main menu entry, instead of having its own top level menu entry.

Admin menu exampleIf (for example) your plugin was called “Gadget Manager” and the CPT it uses is called “Gadgets”, with a taxonomy called “Gadget Types”, then what you’re trying to achieve is something like the image on the right.

In the WordPress developer documentation, the information for register_post_type states that you can enter a top-level menu slug for the show_in_menu parameter. This works fine if you want to move your CPT menu entry under an existing core menu entry, but if you want to place it under a custom menu entry, there’s a problem: you have to register your CPT using the init hook, but you can’t add your custom menu entries until the admin_menu hook, meaning that the register_post_type call doesn’t know about the custom menu entry!

Shorter version of the above paragraph: The information for register_post_type states that you can enter a top-level menu slug for the show_in_menu parameter, but I couldn’t get it to work for a custom menu entry.

Happily there is more than one way to skin a cat, and it just involves a few hooks. Follow the four steps below to get things sorted.

1. Registering your Taxonomy and CPT

First, you register your custom taxonomy and custom post type in the normal way (taxonomy first), by placing the code in a function called by the init hook.

HOWEVER, when adding the arguments for the register_post_type call, the show_in_menu argument should take the value false. But don’t do this for the register_taxonomy call – leave that one as true.

It will look something like this. I’ve removed a lot of the arguments, in particular the labels, to keep things simple. You should use all the arguments you need to use.

function zigpress_init() {
	$args = array(
		'labels' => array(
			'name' => 'Gadget Types',
			'singular_name' => 'Gadget Type',
			'menu_name' => 'Gadget Types',
		),
		'show_in_menu' => true,
		'rewrite' => array('slug'=>'gadget_type'),
	);
	register_taxonomy('gadget_type', array('gadget'), $args);
	$args = array(
		'labels' => array(
			'name' => 'Gadgets',
			'singular_name' => 'Gadget',
			'menu_name' => 'Gadgets'
		),
		'show_in_menu' => false,
		'rewrite' => array('slug'=>'gadgets'),
	);
	register_post_type('gadget', $args);
}
add_action('init', 'zigpress_init');

Now you have your CPT and taxonomy set up but it won’t show in the admin menu at all.

2. Setting up your custom menu entries

Now we have to create our custom menu section. The code below will produce what is shown in the image above.

function zigpress_admin_menu() {
	# First, the top level menu entry
	add_menu_page(
		'Gadget Manager', # Page title
		'Gadget Manager', # Menu title
		'manage_options', # Capability
		'gadget_manager', # Menu slug
		'function_to_render_overview_page_content'
	);
	# Now let's add the initial submenu overview page just as an example
	add_submenu_page(
		'gadget_manager', # Parent menu slug
		'Overview',
		'Overview',
		'manage_options',
		'gadget_manager', # Default subpage so menu slug is same as parent
		'function_to_render_overview_page_content'
	);
	# Now we add our CPT page
	add_submenu_page(
		'gadget_manager',
		'Gadgets',
		'Gadgets',
		'manage_options',
		'edit.php?post_type=gadget'
	);
	# Now we add our taxonomy page
	add_submenu_page(
		'gadget_manager',
		'Gadget Types',
		'Gadget Types',
		'manage_options',
		'edit-tags.php?taxonomy=gadget_type'
	);
	# Let's add another normal admin subpage just for the heck of it
	add_submenu_page(
		'gadget_manager', 
		'Settings',
		'Settings',
		'manage_options',
		'gadget_manager_settings',
		'function_to_render_settings_page_content'
	);
}
add_action('admin_menu', 'zigpress_admin_menu');

However, we still have a problem. The CPT and taxonomy entries show in your custom menu until you actually click one of them, and then they will jump out of sequence and be shown on their own, just like posts and categories are. We solve this with another hook.

3. Keeping the CPT menu entries in place when actually viewing them

To keep the CPT and taxonomy menu entries within your custom menu section even when you’re using those pages, we have to tell the menu system what their parents are, like this:

function zigpress_parent_file($parent_file) {
	global $current_screen;
	if ($current_screen->post_type == 'gadget') {
		if (in_array($current_screen->base, array('post', 'edit')) !== false) {
			return 'gadget_manager'; # Parent menu slug
		}
	}
	if ($current_screen->taxonomy == 'gadget_type') {
		if (in_array($current_screen->base, array('term', 'edit-tags')) !== false) {
			return 'gadget_manager';
		}
	}
	return $parent_file;
}
add_filter('parent_file', 'zigpress_parent_file');

Now the CPT and taxonomy menu entries will stay in place in your custom menu section when these pages are being viewed.

There’s one final problem. If you edit or add a gadget or gadget type, the Gadgets and Gadget Types menu entries are no longer highlighted in bright white to show where you are. We can solve this with a final additional hook.

4. Highlighting the CPT menu entries when adding/editing

By hooking into the submenu_file filter, we can resolve this final issue. This hook tells the menu system which entry to highlight when a certain page is in use.

function zigpress_submenu_file($submenu_file) {
	global $current_screen;
	if ($current_screen->post_type == 'gadget') {
		if (in_array($current_screen->base, array('post', 'edit')) !== false) {
			return 'edit.php?post_type=gadget';
		}
	}
	if ($current_screen->taxonomy == 'gadget_type') {
		if (in_array($current_screen->base, array('term', 'edit-tags')) !== false) {
			return 'edit-tags.php?taxonomy=gadget_type';
		}
	}
	return $submenu_file;
}
add_filter('submenu_file', 'zigpress_submenu_file');

Now everything should behave correctly.

Summary

The above code is quite minimalist – as stated above, many of the CPT arguments were removed, and I haven’t added a menu icon or specified admin menu order, but if you’re a plugin developer these things shouldn’t be an issue, and you should be able to see how to integrate this technique into your plugin.

If you have a CPT but no taxonomy, simply remove all the code relating to gadget_type. And if you have more than one CPT or taxonomy, duplicate the relevant sections of code within the functions as needed.

This code has been tested on ClassicPress 1.1.2 and WordPress 5.4.1.

This post was inspired by the Shellcreeper article How to Add WordPress CPT Admin Menu as Sub Menu which showed how to add a CPT as a submenu entry of a core menu entry.

No Comments

There are no comments yet.

Add a Comment

If you have used this form and would like a copy of the information held about you on this website, or would like the information deleted, please email [email protected].