1

I am looking to add a new, custom menu item to the WordPress admin sidebar that displays pages that use a certain template. For example: a group of pages uses the template called "Retailer Sendout" (page-retailer-sendout.php) would be displayed below the default "Pages" top-level menu item.

I have tried the following URL combinations, but none of them have worked so far.

I have also tested some admin menu customization plugins, but they do not offer the ability to link to pages with a specific template name.

I am comfortable with adding a custom function to the theme's functions.php file, but I do not know where to start.

Any help is greatly appreciated.

1 Answer 1

4

If this is what you want:

Sample Output

... you can do it like so:

Step #1: Add the custom menu item (Retailer Sendout).

function add_retailer_sendout_admin_menu() {
    $slug = 'edit.php?post_type=page&template=page-retailer-sendout.php';

    add_menu_page( 'Retailer Sendout', 'Retailer Sendout', 'edit_pages', $slug,
        '', 'dashicons-admin-page', 19 );
}
add_action( 'admin_menu', 'add_retailer_sendout_admin_menu' );

Notes:

  1. I'm using add_menu_page() to add the menu (which is a top-level menu) with the permission edit_pages and the icon dashicons-admin-page — just check the reference for more details about the syntax and parameters, but the position (the 7th parameter) is set to 19 which would put the menu above the Pages menu.

  2. If you change the name of the query string template, you also need to change it in both steps #2 and #3 below. But if you change just the value, you only need to also change it in step #2 below.

Step #2: Highlight the menu item (this adds current to the item/li class) after you clicked on the link.

Because the 'Pages' and 'Pages » All Pages' menu slug is edit.php?post_type=page, we have to make sure WordPress doesn't override the $parent_file (i.e. the above $slug value) as set in highlight_retailer_sendout_admin_menu() below, so the fix_admin_parent_file_override(), although is a hack/trick, is needed — you could just add the CSS class current to the Retailer Sendout menu, but you'd end up with two/three highlighted menus..

And even though I'm not changing the CSS class, you can use the function to add your custom class(es) and/or remove/edit existing ones. Also, just so you know, the $menu is an array of the top-level menus only, so I use the $submenu to access the sub-menus. I also use $pagenow because at this point, the current screen (see get_current_screen()) hasn't been setup yet.

function fix_admin_parent_file_override( $menu ) {
    global $pagenow, $submenu;

    // If we're NOT on the edit.php page, do nothing.
    if ( ! is_admin() || 'edit.php' !== $pagenow ) {
        return $menu;
    }

    // Same as in the above add_retailer_sendout_admin_menu().
    $slug = 'edit.php?post_type=page&template=page-retailer-sendout.php';

    // We have to make sure the $parent_file is not overriden (by WordPress) to
    // the 'Pages' menu.
    if ( ! empty( $_GET['template'] ) &&
        'page-retailer-sendout.php' === $_GET['template'] ) {
        // Change the 'Pages' URL.
        $menu[20][2] .= '&template=';
        $submenu[ $menu[20][2] ] = $submenu['edit.php?post_type=page'];

        // Change the 'All Pages' URL.
        $submenu[ $menu[20][2] ][5][2] = $menu[20][2];

        unset( $submenu['edit.php?post_type=page'] );
    }

    return $menu;
}
add_filter( 'add_menu_classes', 'fix_admin_parent_file_override' );

And next, we set the $parent_file — note that in the above, we don't set the $parent_file, we only make sure WordPress doesn't override the value afterwards. So for this part, we should use the parent_file hook:

function highlight_retailer_sendout_admin_menu( $parent_file ) {
    // Make sure the PARENT slug is the one for the 'Pages' / 'All Pages' menu.
    if ( 'edit.php?post_type=page' === $parent_file &&
        ( ! empty( $_GET['template'] ) )            &&
        // ..and make sure the template being queried is page-retailer-sendout.php.
        'page-retailer-sendout.php' === $_GET['template'] )
    {
        // Return the $slug value as in the above add_retailer_sendout_admin_menu()
        return 'edit.php?post_type=page&template=page-retailer-sendout.php';
    }
    return $parent_file;
}
add_filter( 'parent_file', 'highlight_retailer_sendout_admin_menu' );

Step #3: Filter the pages by the template.

The template name is stored in a private metadata named _wp_page_template, so we filter the posts/pages query by adding a meta query for that metadata.

And we're using the pre_get_posts hook which also runs on the public side of the site, so we perform checks like is_admin() and is_main_query() to prevent messing with other WP_Query calls (or posts requests).

function filter_admin_pages_by_template( $query ) {
    // Make sure we run the filter only on the admin side of the site (wp-admin)
    // and that the query is the main query.
    if ( is_admin() && $query->is_main_query() &&
        ( ! empty( $_GET['template'] ) )       &&
        // ..and also, check if we're on the edit.php?post_type=page screen.
        'edit-page' === get_current_screen()->id )
    {
        $meta_query = (array) $query->get( 'meta_query' );
        $meta_query[] = array(
            'key'   => '_wp_page_template',
            'value' => $_GET['template'],
        );
        $query->set( 'meta_query', $meta_query );
    }
}
add_action( 'pre_get_posts', 'filter_admin_pages_by_template' );

And actually, you can use the code to filter any templates.. not just the page-retailer-sendout.php as in the question. :)

7
  • Thanks for the detailed and answer to my question. The code is working almost as needed with one catch. Is there any way to have an alias for "Retailer Sendout" appear above the "Pages" parent? Mar 27, 2020 at 18:27
  • So you want to have both Pages -> Retailer Sendout and a top-level menu item named Retailer Sendout? Or is it just the top-level menu? Either way, is the top-level menu going to have sub-menus?
    – Sally CJ
    Mar 28, 2020 at 1:06
  • Just a top-level menu item would be preferred. There would no need for second-level menu items as "Retailer Sendout" is a single page template type. Mar 28, 2020 at 1:13
  • That's what I thought, but I was just confused on that 'alias'.. :) But okay then, I'll revise the answer.
    – Sally CJ
    Mar 28, 2020 at 1:17
  • 1
    Thanks for the code adjustments. They work just as expected. Your help was greatly appreciated. Mar 28, 2020 at 13:51

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

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