WordPress 主题后台菜单开发完全指南


为 WordPress 主题添加设置页面,是让主题具备可配置能力的关键一步。虽然现在更推荐使用自定义器(Customizer)来实现主题选项,但传统的后台菜单方式依然被大量主题采用,理解它的原理对阅读和修改现有主题代码仍然非常重要。

这篇文章带你从头梳理 WordPress 后台菜单的构建逻辑,从顶层菜单到子菜单,从菜单位置的选择到实际页面的内容输出,每一步都会配合代码实例说明。如果你是刚接触 WordPress 主题开发,也能通过这份笔记掌握菜单系统的核心运作方式。


一、菜单应该放在哪里?

WordPress 后台的菜单分为两大类:顶层菜单(显示在侧边栏的一级菜单)和子菜单(点击顶层菜单后展开的二级菜单)。

大多数主题只需要在现有菜单下添加一个子菜单项即可,很少需要创建全新的顶层菜单。WordPress 官方文档中也提到:只有当你引入了一个全新的概念(比如活动管理、会议管理),并且需要多个相关页面来支撑它时,才考虑创建新的顶层菜单。

下表整理了 WordPress 默认顶层菜单对应的菜单别名(slug),创建子菜单时需要用到:

顶层菜单父级别名(parent_slug)
仪表盘index.php
文章edit.php
媒体upload.php
链接link-manager.php
页面edit.php?post_type=page
评论edit-comments.php
外观themes.php
插件plugins.php
用户users.php
工具tools.php
设置options-general.php

注意:自定义文章类型(Custom Post Type)注册后,WordPress 会自动创建对应的顶层菜单,它的父级别名格式为 edit.php?post_type=你的文章类型名称。


二、整体流程:三步走

添加一个后台菜单,无论顶层还是子菜单,都需要完成三个步骤:

  1. 编写一个函数,函数内部调用 WordPress 提供的菜单添加函数(如 add_menu_page() 或 add_submenu_page())。
  2. 把这个函数挂载到 admin_menu 钩子上。
  3. 编写另一个函数,用于输出菜单点击后显示的页面内容。

初学者容易遗漏的是二步——忘记用 add_action() 将函数挂到 admin_menu 钩子上。如果不做这一步,菜单根本不会显示。

来看一个简单的例子,它在“设置”菜单下添加了一个子菜单项:

php

// 二步:将函数挂载到 admin_menu 钩子
add_action( 'admin_menu', 'my_theme_add_settings_page' );

// 一步:编写菜单构建函数
function my_theme_add_settings_page() {
    add_options_page(
        '主题设置',                // 页面标题
        '主题选项',                // 菜单显示名称
        'manage_options',         // 所需权限
        'my-theme-settings',      // 标识符
        'my_theme_settings_page'  // 页面内容渲染函数
    );
}

// 三步:输出页面内容
function my_theme_settings_page() {
    // 检查用户权限
    if ( ! current_user_can( 'manage_options' ) ) {
        wp_die( '您没有足够的权限访问此页面。' );
    }
    echo '<div class="wrap">';
    echo '<h1>主题设置</h1>';
    echo '<p>这里是设置页面的内容区域。</p>';
    echo '</div>';
}

三、顶层菜单:add_menu_page()

如果你确实需要创建一个新的顶层菜单,使用 add_menu_page() 函数。参数说明如下:

参数说明
page_title页面标题,显示在浏览器标签栏
menu_title菜单中显示的文字
capability访问该菜单所需权限,推荐使用 manage_options
menu_slug菜单的标识符,用于生成 URL
function渲染页面内容的函数名
icon_url菜单图标 URL(可选)
position菜单位置,数值越小越靠前(可选)

php

add_action( 'admin_menu', 'my_theme_add_top_menu' );

function my_theme_add_top_menu() {
    add_menu_page(
        '主题高级设置',
        '主题设置',
        'manage_options',
        'my-theme-main',
        'my_theme_main_page',
        'dashicons-admin-generic',
        30
    );
}

function my_theme_main_page() {
    echo '<div class="wrap"><h1>主题高级设置</h1></div>';
}

关于 position 参数,WordPress 默认菜单的位置是:仪表盘(2)、文章(5)、媒体(10)、页面(20)、评论(25)、外观(60)、插件(65)、用户(70)、工具(75)、设置(80)。你可以参考这些数值来安放自己的菜单。


四、子菜单:add_submenu_page()

子菜单的创建使用 add_submenu_page(),关键参数是一个 parent_slug,它决定了这个子菜单放在哪个顶层菜单下面。

php

add_action( 'admin_menu', 'my_theme_add_sub_menus' );

function my_theme_add_sub_menus() {
    // 在外观菜单下添加子菜单
    add_submenu_page(
        'themes.php',             // 父级菜单:外观
        '主题颜色设置',
        '颜色设置',
        'manage_options',
        'theme-color-settings',
        'theme_color_page'
    );
    
    // 在设置菜单下添加子菜单
    add_submenu_page(
        'options-general.php',    // 父级菜单:设置
        '主题其他设置',
        '其他设置',
        'manage_options',
        'theme-other-settings',
        'theme_other_page'
    );
}

function theme_color_page() {
    echo '<div class="wrap"><h1>颜色设置</h1></div>';
}

function theme_other_page() {
    echo '<div class="wrap"><h1>其他设置</h1></div>';
}

五、快捷包装函数

WordPress 为常见的菜单位置提供了包装函数,省去每次填写 parent_slug 的麻烦:

包装函数对应的顶层菜单
add_dashboard_page()仪表盘
add_posts_page()文章
add_media_page()媒体
add_pages_page()页面
add_comments_page()评论
add_theme_page()外观
add_plugins_page()插件
add_users_page()用户
add_management_page()工具
add_options_page()设置

用法示例:

php

add_action( 'admin_menu', 'my_theme_add_quick_menus' );

function my_theme_add_quick_menus() {
    // 在工具菜单下添加
    add_management_page(
        '数据导入工具',
        '导入数据',
        'manage_options',
        'import-tool',
        'import_tool_page'
    );
    
    // 在设置菜单下添加
    add_options_page(
        '邮件通知设置',
        '邮件设置',
        'manage_options',
        'email-settings',
        'email_settings_page'
    );
}

这些包装函数的参数顺序与 add_submenu_page() 完全一致,只是去掉了一个 parent_slug 参数。


六、带数据存储的设置页面示例

下面是一个完整的设置页面示例,它允许用户保存和更新数据到数据库:

php

add_action( 'admin_menu', 'my_theme_add_option_page' );

function my_theme_add_option_page() {
    add_options_page(
        '主题偏好设置',
        '偏好设置',
        'manage_options',
        'theme-preferences',
        'theme_preferences_page'
    );
}

function theme_preferences_page() {
    // 权限检查
    if ( ! current_user_can( 'manage_options' ) ) {
        wp_die( '权限不足' );
    }
    
    $option_name  = 'theme_accent_color';
    $hidden_field = 'theme_pref_submit';
    $data_field   = 'accent_color';
    
    // 获取已保存的值
    $saved_color = get_option( $option_name, '#0073aa' );
    
    // 处理表单提交
    if ( isset( $_POST[ $hidden_field ] ) && $_POST[ $hidden_field ] === 'Y' ) {
        $new_color = sanitize_hex_color( $_POST[ $data_field ] );
        if ( $new_color ) {
            update_option( $option_name, $new_color );
            $saved_color = $new_color;
            echo '<div class="updated"><p>设置已更新。</p></div>';
        }
    }
    ?>
    <div class="wrap">
        <h1>主题偏好设置</h1>
        <form method="post" action="">
            <input type="hidden" name="<?php echo esc_attr( $hidden_field ); ?>" value="Y" />
            <table class="form-table">
                <tr>
                    <th scope="row">
                        <label for="<?php echo esc_attr( $data_field ); ?>">强调色</label>
                    </th>
                    <td>
                        <input type="color" 
                               id="<?php echo esc_attr( $data_field ); ?>" 
                               name="<?php echo esc_attr( $data_field ); ?>" 
                               value="<?php echo esc_attr( $saved_color ); ?>" />
                    </td>
                </tr>
            </table>
            <?php submit_button( '保存设置' ); ?>
        </form>
    </div>
    <?php
}

这个例子中需要注意几个要点:

  • 使用 current_user_can() 做二次权限校验,不能仅依赖菜单的 capability 参数。
  • 使用 update_option() 存储数据,首次调用时会自动在数据库创建记录。
  • 使用 sanitize_hex_color() 对颜色值做安全过滤,永远不要信任用户输入。
  • 提交表单时使用 $_POST 检查,不要直接用 $_REQUEST。

七、页面钩子:精确控制加载行为

add_menu_page() 和 add_submenu_page() 等函数会返回一个值,称为“页面钩子后缀”(Page Hook Suffix)。利用这个值,可以为特定页面挂载专属的 action。

一个常见的应用场景:在主题选项页面加载时,通过 load-{page_hook} 钩子来执行一些初始化操作,比如注册设置项、处理表单提交前的数据校验等。

php

add_action( 'admin_menu', 'my_theme_menu_with_hook' );

function my_theme_menu_with_hook() {
    // 获取页面钩子
    $hook = add_options_page(
        '高级设置',
        '高级设置',
        'manage_options',
        'advanced-settings',
        'advanced_settings_page'
    );
    
    // 仅当此页面加载时执行
    add_action( 'load-' . $hook, 'my_theme_on_settings_load' );
}

function my_theme_on_settings_load() {
    // 在这里做初始化工作
    // 比如注册设置、处理批量操作等
    error_log( '高级设置页面已加载' );
}

function advanced_settings_page() {
    echo '<div class="wrap"><h1>高级设置</h1></div>';
}

另一个实用场景是:如果主题尚未配置,可以在所有后台页面显示提示,但不在主题自己的设置页面显示,避免干扰用户操作。


八、开发过程中的重要提醒

菜单代码在每次后台页面加载时都会执行,所以添加或移除菜单后,刷新后台即可看到变化。不需要重新激活主题,菜单数据也不会被存入数据库。

权限参数应始终使用 manage_options 这类 capability,不要使用用户级别(User Levels)——后者在 WordPress 3.0 之后已被标记为过时。

页面内容渲染函数中必须再次校验权限,这是一个安全底线。即使菜单本身只对特定角色可见,也应在页面加载时做二次检查。


总结

WordPress 后台菜单系统的核心逻辑围绕 admin_menu 钩子展开。开发者需要编写菜单构建函数并挂载到此钩子上,再编写独立的页面渲染函数来呈现具体内容。菜单位置的选择应遵循 WordPress 的惯例——大多数情况下,把子菜单放在外观或设置菜单下就已足够。对于需要存储数据的页面,结合 get_option() 和 update_option() 可以快速实现数据的读写,而页面钩子则提供了更精细的加载控制能力