add_menu_page()
云策文档标注
概述
add_menu_page() 函数用于在 WordPress 管理后台添加一个顶级菜单页面。它通过指定页面标题、菜单标题、权限、菜单 slug 等参数来定义菜单项,并可与回调函数结合以输出页面内容。
关键要点
- 函数参数包括 $page_title(页面标题)、$menu_title(菜单标题)、$capability(所需权限)、$menu_slug(唯一菜单 slug)、$callback(回调函数)、$icon_url(图标 URL)和 $position(菜单位置)。
- 权限检查:函数基于 $capability 决定菜单是否显示,但回调函数也应验证用户权限以确保安全。
- 菜单位置:从 WordPress 4.4 起,位置数字无需唯一以避免冲突;默认位置值可参考文档中的菜单结构列表。
- 图标支持:$icon_url 可接受 base64 编码的 SVG(需以 'data:image/svg+xml;base64,' 开头)、Dashicons 类名或 'none'。
- 钩子使用:通常通过 admin_menu 钩子调用此函数,过早挂钩可能导致权限错误。
- 返回值为页面的 hook_suffix,可用于后续操作。
代码示例
// 示例:添加自定义菜单页面
add_action( 'admin_menu', 'wpdocs_register_my_custom_menu_page' );
function wpdocs_register_my_custom_menu_page() {
add_menu_page(
__( 'Custom Menu Title', 'textdomain' ),
'custom menu',
'manage_options',
'custompage',
'my_custom_menu_page',
plugins_url( 'myplugin/images/icon.png' ),
6
);
}
function my_custom_menu_page() {
esc_html_e( 'Admin Page Test', 'textdomain' );
}注意事项
- 菜单 slug 应唯一且仅包含小写字母、数字、短横线和下划线,以兼容 sanitize_key()。
- 使用 Settings API 保存数据时,若非管理员用户需要访问,需通过 option_page_capability_{$option_group} 钩子修改权限。
- SVG 图标需清理并添加 fill="black" 属性以确保正确着色;建议硬编码 base64 字符串以提高性能。
- 顶级菜单项会自动成为第一个子菜单项;如需不同名称,可结合 add_submenu_page() 调整。
原文内容
Adds a top-level menu page.
Description
This function takes a capability which will be used to determine whether or not a page is included in the menu.
The function which is hooked in to handle the output of the page must check that the user has the required capability as well.
Parameters
$page_titlestringrequired-
The text to be displayed in the title tags of the page when the menu is selected.
$menu_titlestringrequired-
The text to be used for the menu.
$capabilitystringrequired-
The capability required for this menu to be displayed to the user.
$menu_slugstringrequired-
The slug name to refer to this menu by. Should be unique for this menu page and only include lowercase alphanumeric, dashes, and underscores characters to be compatible with sanitize_key() .
$callbackcallableoptional-
The function to be called to output the content for this page.
$icon_urlstringoptional-
The URL to the icon to be used for this menu.
- Pass a base64-encoded SVG using a data URI, which will be colored to match the color scheme. This should begin with
'data:image/svg+xml;base64,'. - Pass the name of a Dashicons helper class to use a font icon, e.g.
'dashicons-chart-pie'. - Pass
'none'to leave div.wp-menu-image empty so an icon can be added via CSS.
- Pass a base64-encoded SVG using a data URI, which will be colored to match the color scheme. This should begin with
$positionint|floatoptional-
The position in the menu order this item should appear.
Default:
null
Source
function add_menu_page( $page_title, $menu_title, $capability, $menu_slug, $callback = '', $icon_url = '', $position = null ) {
global $menu, $admin_page_hooks, $_registered_pages, $_parent_pages;
$menu_slug = plugin_basename( $menu_slug );
$admin_page_hooks[ $menu_slug ] = sanitize_title( $menu_title );
$hookname = get_plugin_page_hookname( $menu_slug, '' );
if ( ! empty( $callback ) && ! empty( $hookname ) && current_user_can( $capability ) ) {
add_action( $hookname, $callback );
}
if ( empty( $icon_url ) ) {
$icon_url = 'dashicons-admin-generic';
$icon_class = 'menu-icon-generic ';
} else {
$icon_url = set_url_scheme( $icon_url );
$icon_class = '';
}
$new_menu = array( $menu_title, $capability, $menu_slug, $page_title, 'menu-top ' . $icon_class . $hookname, $hookname, $icon_url );
if ( null !== $position && ! is_numeric( $position ) ) {
_doing_it_wrong(
__FUNCTION__,
sprintf(
/* translators: %s: add_menu_page() */
__( 'The seventh parameter passed to %s should be numeric representing menu position.' ),
'<code>add_menu_page()</code>'
),
'6.0.0'
);
$position = null;
}
if ( null === $position || ! is_numeric( $position ) ) {
$menu[] = $new_menu;
} elseif ( isset( $menu[ (string) $position ] ) ) {
$collision_avoider = base_convert( substr( md5( $menu_slug . $menu_title ), -4 ), 16, 10 ) * 0.00001;
$position = (string) ( $position + $collision_avoider );
$menu[ $position ] = $new_menu;
} else {
/*
* Cast menu position to a string.
*
* This allows for floats to be passed as the position. PHP will normally cast a float to an
* integer value, this ensures the float retains its mantissa (positive fractional part).
*
* A string containing an integer value, eg "10", is treated as a numeric index.
*/
$position = (string) $position;
$menu[ $position ] = $new_menu;
}
$_registered_pages[ $hookname ] = true;
// No parent as top level.
$_parent_pages[ $menu_slug ] = false;
return $hookname;
}
Changelog
| Version | Description |
|---|---|
| 1.5.0 | Introduced. |
Skip to note 12 content
Codex
Examples
Add a custom menu item to the WordPress admin menu, for a user with administrator capability:
Method 1:
/** * Register a custom menu page. */ function wpdocs_register_my_custom_menu_page() { add_menu_page( __( 'Custom Menu Title', 'textdomain' ), 'custom menu', 'manage_options', 'myplugin/myplugin-admin.php', '', plugins_url( 'myplugin/images/icon.png' ), 6 ); } add_action( 'admin_menu', 'wpdocs_register_my_custom_menu_page' );With this method the page-generating code should be located in
myplugin/myplugin-admin.php:Method 2:
/** * Register a custom menu page. */ function wpdocs_register_my_custom_menu_page(){ add_menu_page( __( 'Custom Menu Title', 'textdomain' ), 'custom menu', 'manage_options', 'custompage', 'my_custom_menu_page', plugins_url( 'myplugin/images/icon.png' ), 6 ); } add_action( 'admin_menu', 'wpdocs_register_my_custom_menu_page' ); /** * Display a custom menu page */ function my_custom_menu_page(){ esc_html_e( 'Admin Page Test', 'textdomain' ); }Skip to note 13 content
surwan
To add a top-level menu item which resolves to a sub-menu item, the menu slugs of each add_menu_page and add_submenu_page calls need to match. This is useful for when you need the top-level menu item label to be different to the first sub-level item. Otherwise the top-level item label is repeated as the first sub-level item.
Minimal example –
$menu_slug = 'wpdocs-orders-slug'; add_menu_page( 'WP Docs Orders', 'WP Docs Orders', 'read', $menu_slug, false ); add_submenu_page( $menu_slug, 'Existing WP Docs Orders', 'Existing WP Docs Orders', 'read', $menu_slug, 'wpdocs_orders_function' );In this example; when the ‘WP Docs Orders’ top-level item is selected, it will trigger the submenu item.
Skip to note 14 content
Felipe Elia
If you pass a base64-encoded SVG using a data URI, it will only be colored to match the color scheme IF it has a
fillstyle attribute. The process is done bywp-admin/js/svg-painter.js.$icon_url).Skip to note 15 content
5Diraptor
Adding custom SVG icons.
I wrestled with this a bit and wanted to add in some notes when using the $icon_url parameter. From my experience and from looking on forums, using your custom icon isn’t as easy as just creating your SVG and encoding it. Here’s the step by step process to add custom SVG icon to the admin menu:
'data:image/svg+xml;base64,'So let me expand on those steps.
Step 1
You can create your SVG graphic in any way you want as far as I can see. I created mine in Adobe Illustrator, but anything like Inkscape, Corel draw, even hardcoding it is ok.
Step 2
The painful part is cleaning up your SVG. After a lot of testing and experimenting I found that the cleaner the SVG is, the more likely it is to work. Basically, a lot of graphics programs will add in metadata and other fluff to make the SVG more widely compatible. That’s great 99% of the time, but when using it for a wordpress menu icon, it just breaks it.
This is an example of an SVG exported normally from Adobe Illustrator:
<svg xmlns="<a href="http://www.w3.org/2000/svg"" rel="nofollow ugc">http://www.w3.org/2000/svg"</a>; viewBox="0 0 15.7 16"><defs><style>.d{fill:#070707;}</style></defs><g id="a"/><g id="b"><g id="c"><path class="d" d="M15.4,6.5H7.9v3h4.4c-.4,2.1-2.3,3.5-4.4,3.4-2.6-.1-4.6-2.1-4.7-4.7-.1-2.7,2-5 ,4.7-5.1,1.1,0,2.2,.4,3.1,1.2l2.3-2.2c-1.4-1.4-3.4-2.1-5.3-2.1C3.6,0,0,3.6,0,8s3.6,8,8,8, 7.7-3.2,7.7-7.8c-.1-.6-.1-1.1-.3-1.7Z"/><path class="d" d="M15.4,6.5H7.9v3h4.4c-.4,2.1-2.3, 3.5-4.4,3.4-2.6-.1-4.6-2.1-4.7-4.7-.1-2.7,2-5,4.7-5.1,1.1,0,2.2,.4,3.1,1.2l2.3-2.2c-1.4-1. 4-3.4-2.1-5.3-2.1C3.6,0,0,3.6,0,8s3.6,8,8,8,7.7-3.2,7.7-7.8c-.1-.6-.1-1.1-.3-1.7Z"/></g> </g></svg>Messy, very messy. Even when we add some line breaks and indent it, there’s a lot going on.
<svg xmlns="<a href="http://www.w3.org/2000/svg"" rel="nofollow ugc">http://www.w3.org/2000/svg"</a>; viewBox="0 0 15.7 16"> <defs> <style> .d{fill:#070707;} </style> </defs> <g id="a"/> <g id="b"> <g id="c"> <path class="d" d="M15.4,6.5H7.9v3h4.4c-.4,2.1-2.3,3.5-4.4,3.4-2.6-.1-4.6-2.1-4.7-4.7-.1-2.7,2-5,4.7-5.1,1.1,0,2.2,.4,3.1,1.2l2.3-2.2c-1.4-1.4-3.4-2.1-5.3-2.1C3.6,0,0,3.6,0,8s3.6,8,8,8,7.7-3.2,7.7-7.8c-.1-.6-.1-1.1-.3-1.7Z"/> <path class="d" d="M15.4,6.5H7.9v3h4.4c-.4,2.1-2.3,3.5-4.4,3.4-2.6-.1-4.6-2.1-4.7-4.7-.1-2.7,2-5,4.7-5.1,1.1,0,2.2,.4,3.1,1.2l2.3-2.2c-1.4-1.4-3.4-2.1-5.3-2.1C3.6,0,0,3.6,0,8s3.6,8,8,8,7.7-3.2,7.7-7.8c-.1-.6-.1-1.1-.3-1.7Z"/> </g> </g> </svg>So what we want to do is clean this up a lot and get rid of unnecessary data. You may find that the above will load if you add it as an image source but it won’t color properly with the GUI and it will probably be the wrong size. So there’s a few things we need to do:
tag, this won’t help</li>element completely.<li>Then you will have to hardcode each style into the path, rather than using the tag. For each path, work out which classes apply to the path from the tag, and add the CSS from those classes into the path.</li>
<li>Once that’s done, you can remove the <code>defs
fill="black"to each path.width="20" height="20"into the opening elementOnce that’s done, you’ll end up with an SVG file that looks more like this:
<svg width="20" height="20" viewBox="0 0 20 20" xmlns="<a href="http://www.w3.org/2000/svg">" rel="nofollow ugc">http://www.w3.org/2000/svg"></a>; <path fill="black" d="M17.6 8.5h-7.5v3h4.4c-.4 2.1-2.3 3.5-4.4 3.4-2.6-.1-4.6-2.1-4.7-4.7-.1-2.7 2-5 4.7-5.1 1.1 0 2.2.4 3.1 1.2l2.3-2.2C14.1 2.7 12.1 2 10.2 2c-4.4 0-8 3.6-8 8s3.6 8 8 8c4.6 0 7.7-3.2 7.7-7.8-.1-.6-.1-1.1-.3-1.7z" fillrule="evenodd" cliprule="evenodd"> </path> </svg>Step 3
Now you want to base64 encode your SVG. The way I did it was to pass the file into PHP’s default encoding mechanism, and copy the path into my PHP as an absolute path. You don’t want PHP to read the SVG file, base64 encode it, and pass it to the menu every time the user reloads the page, that’s just a waste of resources and time.
// load the SVG data by loading the file or including the XML directly. $svg = file_get_contents( '/path/to/icon.svg' ); // or $svg = '<svg width="20" height="20" viewBox="0 0 20 20" xmlns="<a href="http://www.w3.org/2000/svg"><path" rel="nofollow ugc">http://www.w3.org/2000/svg"><path</a> fill="black" d="M17.6 8.5h-7.5v3h4.4c-.4 2.1-2.3 3.5-4.4 3.4-2.6-.1-4.6-2.1-4.7-4.7-.1-2.7 2-5 4.7-5.1 1.1 0 2.2.4 3.1 1.2l2.3-2.2C14.1 2.7 12.1 2 10.2 2c-4.4 0-8 3.6-8 8s3.6 8 8 8c4.6 0 7.7-3.2 7.7-7.8-.1-.6-.1-1.1-.3-1.7z" fillrule="evenodd" cliprule="evenodd"></path></svg>'; // then encode it and echo it out echo base64_encode( $svg ); // will give you a long string that looks like this: // PHN2ZyB3aWR0aD0iMjAiIGhlaWdodD0iMjAiIHZpZXdib3g9IjAgMCAyMCAyMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBmaWxsPSIjYTdhYWFkIiBkPSJNMTcuNiA4LjVoLTcuNXYzaDQuNGMtLjQgMi4xLTIuMyAzLjUtNC40IDMuNC0yLjYtLjEtNC42LTIuMS00LjctNC43LS4xLTIuNyAyLTUgNC43LTUuMSAxLjEgMCAyLjIuNCAzLjEgMS4ybDIuMy0yLjJDMTQuMSAyLjcgMTIuMSAyIDEwLjIgMmMtNC40IDAtOCAzLjYtOCA4czMuNiA4IDggOGM0LjYgMCA3LjctMy4yIDcuNy03LjgtLjEtLjYtLjEtMS4xLS4zLTEuN3oiIGZpbGxydWxlPSJldmVub2RkIiBjbGlwcnVsZT0iZXZlbm9kZCI+PC9wYXRoPjwvc3ZnPg== // thats your base64 encoded SVG file!Step 4
Add ‘data:image/svg+xml;’ in front of the base64 SVG so it looks like this:
"data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAiIGhlaWdodD0iMjAiIHZpZXdib3g9IjAgMCAyMCAyMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBmaWxsPSIjYTdhYWFkIiBkPSJNMTcuNiA4LjVoLTcuNXYzaDQuNGMtLjQgMi4xLTIuMyAzLjUtNC40IDMuNC0yLjYtLjEtNC42LTIuMS00LjctNC43LS4xLTIuNyAyLTUgNC43LTUuMSAxLjEgMCAyLjIuNCAzLjEgMS4ybDIuMy0yLjJDMTQuMSAyLjcgMTIuMSAyIDEwLjIgMmMtNC40IDAtOCAzLjYtOCA4czMuNiA4IDggOGM0LjYgMCA3LjctMy4yIDcuNy03LjgtLjEtLjYtLjEtMS4xLS4zLTEuN3oiIGZpbGxydWxlPSJldmVub2RkIiBjbGlwcnVsZT0iZXZlbm9kZCI+PC9wYXRoPjwvc3ZnPg=="and that there, sir, is a perfectly valid
$icon_urlstring. I would hard code that string to a variable and simply add it into your plugin every time.add_menu_page( __( 'WPDocs Plugin page name' ), __( 'WPDocs Plugin' ), 'manage_options', 'slug', 'callback','data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjAiIGhlaWdodD0iMjAiIHZpZXdib3g9IjAgMCAyMCAyMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBmaWxsPSIjYTdhYWFkIiBkPSJNMTcuNiA4LjVoLTcuNXYzaDQuNGMtLjQgMi4xLTIuMyAzLjUtNC40IDMuNC0yLjYtLjEtNC42LTIuMS00LjctNC43LS4xLTIuNyAyLTUgNC43LTUuMSAxLjEgMCAyLjIuNCAzLjEgMS4ybDIuMy0yLjJDMTQuMSAyLjcgMTIuMSAyIDEwLjIgMmMtNC40IDAtOCAzLjYtOCA4czMuNiA4IDggOGM0LjYgMCA3LjctMy4yIDcuNy03LjgtLjEtLjYtLjEtMS4xLS4zLTEuN3oiIGZpbGxydWxlPSJldmVub2RkIiBjbGlwcnVsZT0iZXZlbm9kZCI+PC9wYXRoPjwvc3ZnPg==' );I hope that helps someone else, because that would have saved me several hours this morning!
Skip to note 16 content
Sophy
Add Menu Pages with Dashicons. More dashicons check here https://developer.wordpress.org/resource/dashicons/#menu
add_action( 'admin_menu', 'register_my_custom_menu_page' ); function register_my_custom_menu_page() { // add_menu_page( $page_title, $menu_title, $capability, $menu_slug, $function, $icon_url, $position ); add_menu_page( 'Custom Menu Page Title', 'Custom Menu Page', 'manage_options', 'custom.php', '', 'dashicons-welcome-widgets-menus', 90 ); }Skip to note 17 content
Aamer Shahzad
/** * Create admin Page to list unsubscribed emails. */ // Hook for adding admin menus add_action('admin_menu', 'wpdocs_unsub_add_pages'); // action function for above hook /** * Adds a new top-level page to the administration menu. */ function wpdocs_unsub_add_pages() { add_menu_page( __( 'Unsub List', 'textdomain' ), __( 'Unsub Emails','textdomain' ), 'manage_options', 'wpdocs-unsub-email-list', 'wpdocs_unsub_page_callback', '' ); } /** * Disply callback for the Unsub page. */ function wpdocs_unsub_page_callback() { echo 'Unsubscribe Email List'; }Skip to note 18 content
eliran
A better example to peskyesky.
object oriented example.
TODO:
create includes/layout.php
class WPDocs_EB_EbtechModules { public static function init() { add_action( 'admin_enqueue_scripts', array( __CLASS__, 'adminAssets' ) ); add_action( 'admin_menu', array( __CLASS__, 'adminMenu' ) ); } public static function adminMenu() { add_menu_page( __( 'WPDocs Ebtech Menu', 'wpdocs-webnail-modules' ), __( 'WPDocs Ebtech modules', 'wpdocs-webnail-modules' ), 'manage_options', 'wpdocs-ebtech-modules', array( __CLASS__, 'menuPage' ), 'dashicons-tagcloud', 6 ); } public static function menuPage() { if ( is_file( plugin_dir_path( __FILE__ ) . 'includes/layout.php' ) ) { include_once plugin_dir_path( __FILE__ ) . 'includes/layout.php'; } } public static function getSettings() { return get_option( 'wpdocs_ebtech_modules_option' ); } public static function adminAssets() { if ( isset( $_GET['page'] ) && ! empty( $_GET['page'] ) && 'wpdocs-ebtech-modules' === $_GET['page'] ) { } } } WPDocs_EB_EbtechModules::init(); WPDocs_EB_EbtechModules::getSettings();Skip to note 19 content
Vincent Dubroeucq
The callback function used to display the contents of the page isn’t necessarily required to check current user’s capabilities, since the callback is not even hooked in if the user doesn’t have the right capabilities.
If you try to directly access a page for which you don’t have required capabilities, you’ll get an error page.
Skip to note 20 content
angelbouklis
Quick example on how to add a menu entry with an icon.
</pre> </div><!-- .comment-content --> <section id='feedback-6596' class='wporg-has-embedded-code feedback hide-if-js' data-comment-count='0'> </section><!-- .feedback --> <footer class='feedback-links wporg-dot-link-list' > <a role="button" class="feedback-login" href="https://login.wordpress.org/?redirect_to=https%3A%2F%2Fdeveloper.wordpress.org%2Freference%2Ffunctions%2Fadd_menu_page%2F%3Freplytocom%3D6596%23feedback-editor-6596" rel="nofollow">Log in to add feedback</a></footer> </article><!-- .comment-body --> </li> <li id="comment-6994" data-comment-id="6994" class="comment byuser comment-author-sarankumar even thread-even depth-1"> <article id="div-comment-6994" class="comment-body"> <a href="#comment-content-6994" class="screen-reader-text">Skip to note 21 content</a> <header class="comment-meta"> <div class="comment-author vcard"> <span class="comment-author-attribution"> <a href="https://profiles.wordpress.org/sarankumar/" rel="external nofollow" class="url">Sarankumar</a> </span> <a class="comment-date" href="https://developer.wordpress.org/reference/functions/add_menu_page/#comment-6994"> <time datetime="2024-05-01T12:14:50+00:00"> 2 years ago </time> </a> </div> <div class="user-note-voting" data-nonce="564cc0b147" data-can-vote="false"><a class="user-note-voting-up" title="You must log in to vote on the helpfulness of this note" data-id="6994" data-vote="up" href="https://login.wordpress.org?redirect_to=https%3A%2F%2Fdeveloper.wordpress.org%2Freference%2Ffunctions%2Fadd_menu_page%2F%23comment-6994"><span class="screen-reader-text">You must log in to vote on the helpfulness of this note</span></a><span class="user-note-voting-count " title=""><span class="screen-reader-text">Vote results for this note: </span>0</span><a class="user-note-voting-down" title="You must log in to vote on the helpfulness of this note" data-id="6994" data-vote="down" href="https://login.wordpress.org?redirect_to=https%3A%2F%2Fdeveloper.wordpress.org%2Freference%2Ffunctions%2Fadd_menu_page%2F%23comment-6994"><span class="screen-reader-text">You must log in to vote on the helpfulness of this note</span></a></div> </header> <!-- .comment-metadata --> <div class="wporg-has-embedded-code comment-content" id="comment-content-6994"> <p>When incorporating additional menu items using <strong>add_menu_page</strong>, the main page automatically becomes the first entry in the submenu. However, if you wish to assign a different name to the first item in the list, you can utilize the following code.</p> <pre class="wp-block-code"><code lang="php" class="language-php line-numbers">add_action( 'admin_menu', 'wpdocs_menu_pages' ); function wpdocs_menu_pages() { add_menu_page( 'My Page Title', 'My Page Title', 'manage_options', 'wpdocs-menu', 'wpdocs_menu_output','dashicons-whatsapp',6 ); add_submenu_page( 'wpdocs-menu', 'Submenu Page Title', 'Submenu Page Title', 'manage_options', 'wpdocs-menu' ); add_submenu_page( 'wpdocs-menu', 'Submenu Page Title2', 'Submenu Page Title 2', 'manage_options', 'wpdocs-menu2' ); }Skip to note 22 content
peskyesky
// For those who are object orientated. Add a class // function as the menu callback and setup the // menus automatically. // Exit if accessed directly if ( !defined( 'ABSPATH' ) ) exit; class MyMenuSetterUpper { private static $instance; /** * Main Instance * * @staticvar array $instance * @return The one true instance */ public static function instance() { if ( ! isset( self::$instance ) ) { self::$instance = new self; self::$instance->addMyAdminMenu(); } return self::$instance; } public function addMyAdminMenu() { add_menu_page( 'My Page Title', 'My Page', 'read', 'my-menu-page-slug', array( $this, 'myAdminPage' ), 'to/icon/file.svg', '2.1' ); } public function myAdminPage() { // Echo the html here... } } // Call the class and add the menus automatically. $MyMenuSetterUpper = MyMenuSetterUpper::instance();