How to Add WordPress Admin Pages

Sometimes a plugin or theme needs a new page in the admin area, this is post will explain how to do this.

There are a number of functions that can be called to make this happen, but they all use add_menu_page or add_submenu_page. Want to see how this all fits together? The code is on github.

The overview of this is that a function that calls add_menu_page, add_submenu_page, one of the other functions described below must be hooked into the admin_menu hook.

So in a normally bootstrapped WordPress plugin, that might look like this.

// hooked into `plugins_loaded` in the main plugin file
function chrisguitarguy_adminpages_load()
{
    add_action('admin_menu', 'chrisguitarguy_adminpages_add');
}

function chrisguitarguy_adminpages_add()
{
    // call `add_menu_page` or `add_submenu_page`, etc
}

Adding a Top Level Menu Page

This is done with add_menu_page.

// hooked into `admin_menu`, see above
function chrisguitarguy_adminpages_add()
{
    add_menu_page(
        // page <title> tag, localize it like any other user-facing string
        __('Example Top-Level Page', 'chrisguitarguy-adminpages'),
        // text displayed in the menu, localize it
        __('Example Top', 'chrisguitarguy-adminpages'),
        // what capability is required to access this? `manage_options` would be
        // an admin-level user.
        'manage_options',
        // page url slug, prefix like anything else
        'chrisguitarguy-adminpages-toplevel',
        // callback, this is called to display the actual page
        'chrisguitarguy_adminpages_callback',
        // this is the icon URL, we'll skip this, but it can be used to
        // show a custom ICON in the menu.
        '',
        // position, where in the menu should this page be displayed?
        // higher number === lower on the page, can use ints or floats here
        1000
    );
}

// show the page
function chrisguitarguy_adminpages_callback()
{
    include __DIR__.'/../views/page.php';
}

Here’s an illustration of where all those values passed end up displayed on the page.

WordPress Admin add_menu_page

On the Admin Page $function

This callable is invoked by WordPress to actually show the page. I like to keep the actual HTML itself in a separate view file.

Whatever the organization, the callback should do two things to conform to the style of the rest of the WordPress admin.

  1. Wrap the entire admin page with a <div class="wrap">.
  2. Use an <h1> to show the page title.
<?php
// in views/page.php
// exit if WordPress isn't loaded
!defined('ABSPATH') && exit;
?>
<div class="wrap">
    <h1>Admin Page Example</h1>
    <?php /* other things here */ ?>
</div>

Adding Submenu Pages

Call add_submenu_page instead of add_menu_page.

// hooked into `admin_menu`, see above
function chrisguitarguy_adminpages_add()
{
    add_submenu_page(
        // the page under which the submenu page should be nested
        'options-general.php',
        // page <title> tag, localize it like any other user-facing string
        __('Example Submenu Page', 'chrisguitarguy-adminpages'),
        // text displayed in the menu, localize it
        __('Example Submenu', 'chrisguitarguy-adminpages'),
        // what capability is required to access this? `manage_options` would be
        // an admin-level user.
        'manage_options',
        // page url slug, prefix like anything else
        'chrisguitarguy-adminpages-submenu',
        // callback, this is called to display the actual page
        'chrisguitarguy_adminpages_callback'
    );
}

Here’s an illustration of where all those variables end up.

WordPress Admin add_submenu_page

There are quite a few other functions that are small wrappers around add_submenu_page that add pages under specific places in the admin area.

function chrisguitarguy_adminpages_add()
{
    // Under Tools
    add_management_page(
        __('Example Management Page', 'chrisguitarguy-adminpages'),
        __('Example Manage', 'chrisguitarguy-adminpages'),
        'manage_options',
        'chrisguitarguy-adminpages-management',
        'chrisguitarguy_adminpages_callback'
    );
    // Under Settings
    add_options_page(
        __('Example Options Page', 'chrisguitarguy-adminpages'),
        __('Example Options', 'chrisguitarguy-adminpages'),
        'manage_options',
        'chrisguitarguy-adminpages-options',
        'chrisguitarguy_adminpages_callback'
    );
    // Under Appearance
    add_theme_page(
        __('Example Theme Page', 'chrisguitarguy-adminpages'),
        __('Example Theme', 'chrisguitarguy-adminpages'),
        'manage_options',
        'chrisguitarguy-adminpages-theme',
        'chrisguitarguy_adminpages_callback'
    );
    // Under Themes
    add_plugins_page(
        __('Example Plugin Page', 'chrisguitarguy-adminpages'),
        __('Example Plugin', 'chrisguitarguy-adminpages'),
        'manage_options',
        'chrisguitarguy-adminpages-plugin',
        'chrisguitarguy_adminpages_callback'
    );
    // Under Users
    add_users_page(
        __('Example User Page', 'chrisguitarguy-adminpages'),
        __('Example User', 'chrisguitarguy-adminpages'),
        'manage_options',
        'chrisguitarguy-adminpages-user',
        'chrisguitarguy_adminpages_callback'
    );
}

WordPress Admin Submenu Pages

Adding Submenus Under Post Types

Submenu pages can be added under post types by using edit.php?post_type={posttype} as the $parent_slug argument.

To add a submenu page under the Pages area, for example:

function chrisguitarguy_adminpages_add()
{
    // Under Pages
    add_submenu_page(
        'edit.php?post_type=page',
        __('Example Post Type Page', 'chrisguitarguy-adminpages'),
        __('Example Type', 'chrisguitarguy-adminpages'),
        'manage_options',
        'chrisguitarguy-adminpages-type',
        'chrisguitarguy_adminpages_callback'
    );
}

WordPress Admin Post Type Submenu

Permission Errors on Admin Pages

If any hook other than one of the admin_menu hooks is used to add the menu pages, a “Sorry, you are not allowed to access this page” message will be show.

Sorry You Are Not Allowed to Access This Page

This is broken:

function chrisguitarguy_adminpages_load()
{
    add_action('admin_init', 'chrisguitarguy_adminpages_add');
}

function chrisguitarguy_adminpages_add()
{
    // ...
}

Always be sure to hook into admin_menu or one of the other hooks described below.

- add_action('admin_init', 'chrisguitarguy_adminpages_add');
+ add_action('admin_menu', 'chrisguitarguy_adminpages_add');

Other Hooks for Adding Menu Pages

One of three hooks may be used to add menu page.

  1. admin_menu — what we’ve used here, this one always fires in single-site admin areas.
  2. network_admin_menu — fires in WordPress multi-site’s network admin area.
  3. user_admin_menu — fires in WordPress multi-site’s user admin area (/wp-admin/user/).

Note that these hooks are exclusive. admin_menu will never fire on a network admin page, so choose the one that makes the most sense for the use case.