Add custom admin menu item for pages using a certain template

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"https://wordpress.stackexchange.com/"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. ๐Ÿ™‚

Leave a Comment