I’m going to assuming when you say that you “hover over it, it shows custom post types displayed in a list” you mean submenus. The below uses admin_menu
to create a blank Parent Menu then we loop through the public post types to display them as submenus:
/**
* Add an Admin Menu which contains submenus of public post types
* - https://codex.wordpress.org/Plugin_API/Action_Reference/admin_menu
* - https://developer.wordpress.org/reference/hooks/admin_menu/
*
* @return void
*/
function wpse_q_241760() {
$post_types = get_post_types( array(
'public' => true,
), 'objects' );
if( ! empty( $post_types ) ) {
add_menu_page(
'Post Types', // Page Title ( doesn't matter )
'Post Types', // Menu Title
'edit_posts', // Capability to view this menu item
'javascript:void(0);', // URL ( slug )
'', // Callback Function
'dashicons-book', // Dashicon
24 // Menu Position
);
foreach( $post_types as $type ) {
add_submenu_page(
'javascript:void(0);', // Parent URL Slug
$type->labels->name, // Page Title ( doesn't matter in this case )
$type->labels->name, // Menu Title
'edit_posts', // Cability to view
"edit.php?post_type={$type->name}" // URL ( slug )
);
}
remove_submenu_page( 'javascript:void(0);', 'javascript:void(0);' );
}
}
add_action( 'admin_menu', 'wpse_q_241760' );
The above add_menu_page()
needs to be passed some sort of link – since we would have nothing to show at that link ( there’s no post_type
tied to this page, it’s a dummy page ) I’ve opted to link to javascript:void(0);
instead which tells our browser not to navigation anywhere. This allows us to instead hover the menu item and click one of the Post Type links. It also added a submenu of the same name but we don’t need it so we remove it at the end of the function call.
The get_post_types()
function has a few parameters to filter on if public
doesn’t work for your case.