[WordPress] 外掛管理選單開發 – 建立後台管理頁面的完整教學

本篇文章更新時間:2026/03/03
如有資訊過時或語誤之處,歡迎使用 Contact 功能通知或向一介資男的 LINE 社群反應。
如果本站內容對你有幫助,歡迎贊助支持


本系列文參考自 WordPress.org 官方外掛開發文件 - Administration Menus 的繁體中文版本,並加入作者實務開發經驗補充。

WordPress 後台選單開發是外掛開發中不可或缺的一環。當你的外掛需要提供設定頁面或管理介面時,就必須透過 Administration Menus API 在後台建立對應的選單項目。WordPress 提供了兩種選單類型:頂層選單(Top-Level Menus)與子選單(Sub-Menus),開發者可以根據外掛的複雜度與功能需求,選擇最適合的方式來建立管理頁面。本篇教學將從基礎的 add_menu_page() 開始,逐步介紹如何建立完整的後台管理選單架構。

頂層選單 (Top-Level Menus)

頂層選單會顯示在 WordPress 後台左側的主選單列中,與「文章」、「頁面」、「設定」等內建選單並列。要建立頂層選單,需要使用 add_menu_page() 函式,並將其掛載在 admin_menu 這個 Action Hook 上。

add_menu_page() 參數說明

add_menu_page(
    string $page_title,
    string $menu_title,
    string $capability,
    string $menu_slug,
    callable $function = '',
    string $icon_url = '',
    int $position = null
);

各參數說明如下:

  • $page_title(字串):頁面的標題,會顯示在瀏覽器的標題列中。
  • $menu_title(字串):選單中顯示的名稱文字。
  • $capability(字串):使用者需要具備的權限能力才能看到此選單項目,例如 manage_options 代表需要管理員權限。
  • $menu_slug(字串):此選單的唯一識別碼(slug),同時也作為頁面的 URL 參數。建議使用外掛名稱作為前綴以避免衝突。
  • $function(回呼函式):當使用者點擊此選單時,負責輸出頁面內容的回呼函式。
  • $icon_url(字串):選單項目旁的圖示 URL。可以是圖片路徑、Dashicons 類別名稱(如 dashicons-admin-generic),或以 base64 編碼的 SVG 圖示。留空則使用預設齒輪圖示。
  • $position(整數):選單在左側選單列中的排列位置。數字越小越靠上方,留空則排在最後。內建選單的位置參考值:2(儀表板)、4(分隔線)、5(文章)、10(媒體)、20(頁面)、25(留言)、60(外觀)、65(外掛)、70(使用者)、75(工具)、80(設定)。

完整範例:建立頂層選單

以下範例展示如何建立一個帶有設定表單的頂層選單頁面:

/**
 * 註冊頂層選單頁面
 */
add_action( 'admin_menu', 'wporg_options_page' );

function wporg_options_page() {
    add_menu_page(
        'WPOrg',                                        // 頁面標題
        'WPOrg Options',                                // 選單名稱
        'manage_options',                               // 所需權限
        'wporg',                                        // 選單 slug
        'wporg_options_page_html',                      // 回呼函式
        plugin_dir_url( __FILE__ ) . 'images/icon.png', // 圖示
        20                                              // 位置(在「頁面」之後)
    );
}

/**
 * 頂層選單頁面的 HTML 輸出
 */
function wporg_options_page_html() {
    // 檢查使用者權限
    if ( ! current_user_can( 'manage_options' ) ) {
        return;
    }
    ?>
    <div class="wrap">
        <h1><?php echo esc_html( get_admin_page_title() ); ?></h1>
        <form action="options.php" method="post">
            <?php
            settings_fields( 'wporg_options' );
            do_settings_sections( 'wporg' );
            submit_button( __( 'Save Settings', 'textdomain' ) );
            ?>
        </form>
    </div>
    <?php
}

在回呼函式中,使用 get_admin_page_title() 來取得頁面標題,而非硬編碼標題文字。外層的 <div class="wrap"> 是 WordPress 後台頁面的標準容器,使用它可以確保你的頁面樣式與後台其他頁面一致。

使用 Dashicons 作為選單圖示

WordPress 內建了 Dashicons 圖示字型,你可以直接使用 Dashicons 的類別名稱作為 $icon_url 參數,而不需要額外準備圖片檔案:

add_menu_page(
    'My Plugin',
    'My Plugin',
    'manage_options',
    'my-plugin',
    'my_plugin_page_html',
    'dashicons-admin-tools', // 使用 Dashicons 圖示
    30
);

常用的 Dashicons 圖示包括:dashicons-admin-generic(齒輪)、dashicons-admin-tools(工具)、dashicons-chart-bar(圖表)、dashicons-shield(盾牌)、dashicons-database(資料庫)等。完整的圖示列表可以在 WordPress Dashicons 官方頁面 查詢。

子選單 (Sub-Menus)

子選單會出現在某個頂層選單的展開列表中。對於只有單一設定頁面的外掛,官方建議將設定頁面加入到既有的頂層選單(如「設定」或「工具」)作為子選單,而非建立新的頂層選單,以避免後台選單過於臃腫。

add_submenu_page() 參數說明

add_submenu_page(
    string $parent_slug,
    string $page_title,
    string $menu_title,
    string $capability,
    string $menu_slug,
    callable $function = ''
);

各參數說明如下:

  • $parent_slug(字串):父層選單的 slug。若要加入到內建選單下,可使用對應的 PHP 檔名,例如 options-general.php(設定)、tools.php(工具)、edit.php(文章)等。若要加入到自訂頂層選單下,則使用該頂層選單的 $menu_slug
  • $page_title(字串):頁面標題,顯示在瀏覽器標題列。
  • $menu_title(字串):子選單中顯示的名稱。
  • $capability(字串):使用者需要具備的權限能力。
  • $menu_slug(字串):此子選單的唯一識別碼。
  • $function(回呼函式):負責輸出頁面內容的回呼函式。

範例:將設定頁面加入「設定」選單下

add_action( 'admin_menu', 'wporg_options_page' );

function wporg_options_page() {
    add_submenu_page(
        'options-general.php',     // 父層選單:設定
        'WPOrg Options',           // 頁面標題
        'WPOrg',                   // 選單名稱
        'manage_options',          // 所需權限
        'wporg',                   // 選單 slug
        'wporg_options_page_html'  // 回呼函式
    );
}

function wporg_options_page_html() {
    if ( ! current_user_can( 'manage_options' ) ) {
        return;
    }
    ?>
    <div class="wrap">
        <h1><?php echo esc_html( get_admin_page_title() ); ?></h1>
        <form action="options.php" method="post">
            <?php
            settings_fields( 'wporg_options' );
            do_settings_sections( 'wporg' );
            submit_button( __( 'Save Settings', 'textdomain' ) );
            ?>
        </form>
    </div>
    <?php
}

在自訂頂層選單下加入子選單

如果你的外掛已經建立了頂層選單,可以在其下方加入多個子選單來組織不同的功能頁面:

add_action( 'admin_menu', 'wporg_register_menus' );

function wporg_register_menus() {
    // 先建立頂層選單
    add_menu_page(
        'WPOrg',
        'WPOrg',
        'manage_options',
        'wporg',
        'wporg_main_page_html',
        'dashicons-admin-generic',
        30
    );

    // 加入子選單:一般設定
    add_submenu_page(
        'wporg',               // 父層選單的 slug
        'General Settings',
        'General',
        'manage_options',
        'wporg',               // 與父層相同 slug,會取代預設的第一個子選單
        'wporg_main_page_html'
    );

    // 加入子選單:進階設定
    add_submenu_page(
        'wporg',
        'Advanced Settings',
        'Advanced',
        'manage_options',
        'wporg-advanced',
        'wporg_advanced_page_html'
    );
}

注意上面範例中,第一個子選單的 $menu_slug 與頂層選單相同(都是 wporg),這是一個常見的技巧。WordPress 在建立頂層選單時,會自動產生一個同名的子選單項目。透過將第一個子選單的 slug 設定為與頂層相同,就可以覆蓋掉預設的子選單名稱,讓選單名稱更有意義。

預定義的子選單輔助函式

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() - 加入到「設定」選單下
  • add_network_admin_page() - 加入到多站點網路管理選單下

這些輔助函式的參數與 add_submenu_page() 相同,只是省略了 $parent_slug 參數。例如,add_options_page() 等同於呼叫 add_submenu_page( 'options-general.php', ... )

移除選單

在某些情況下,你可能需要移除後台中不需要的選單項目。WordPress 提供了 remove_menu_page()remove_submenu_page() 兩個函式來達成這個目的。

remove_menu_page()

移除頂層選單項目:

remove_menu_page( string $menu_slug );

remove_submenu_page()

移除子選單項目:

remove_submenu_page( string $parent_slug, string $menu_slug );

範例:移除不需要的選單

add_action( 'admin_menu', 'wporg_remove_menus', 999 );

function wporg_remove_menus() {
    // 移除「工具」頂層選單
    remove_menu_page( 'tools.php' );

    // 移除「設定」下的「寫作」子選單
    remove_submenu_page( 'options-general.php', 'options-writing.php' );
}

請注意,移除選單並不會真正阻止使用者存取該頁面。如果使用者知道正確的 URL,仍然可以直接輸入網址進入。若要真正限制存取,必須搭配權限檢查來實現。此外,移除選單的 Hook 優先順序應該設定較高的數值(如 999),以確保在所有選單都註冊完成後才執行移除。

表單提交處理

在管理選單頁面中建立表單後,需要正確處理表單的提交。WordPress 建議搭配 Settings API 來處理設定的儲存,這樣可以自動處理 nonce 驗證與資料清理。

使用 Settings API 處理表單

在前面的範例中,我們已經使用了 settings_fields()do_settings_sections()。表單的 action 指向 options.php,這是 WordPress 內建的設定處理程式。完整的流程如下:

/**
 * 註冊設定欄位
 */
add_action( 'admin_init', 'wporg_settings_init' );

function wporg_settings_init() {
    // 註冊設定群組
    register_setting( 'wporg_options', 'wporg_field_api_key', array(
        'type'              => 'string',
        'sanitize_callback' => 'sanitize_text_field',
        'default'           => '',
    ) );

    // 新增設定區段
    add_settings_section(
        'wporg_section_general',
        __( 'General Settings', 'textdomain' ),
        'wporg_section_general_callback',
        'wporg'
    );

    // 新增設定欄位
    add_settings_field(
        'wporg_field_api_key',
        __( 'API Key', 'textdomain' ),
        'wporg_field_api_key_callback',
        'wporg',
        'wporg_section_general'
    );
}

function wporg_section_general_callback() {
    echo '<p>' . __( 'Enter your API settings below.', 'textdomain' ) . '</p>';
}

function wporg_field_api_key_callback() {
    $value = get_option( 'wporg_field_api_key' );
    echo '<input type="text" name="wporg_field_api_key" value="' . esc_attr( $value ) . '" class="regular-text">';
}

自行處理表單提交

如果你不使用 Settings API,也可以自行處理表單提交。此時必須注意安全性驗證:

function wporg_custom_page_html() {
    if ( ! current_user_can( 'manage_options' ) ) {
        return;
    }

    // 處理表單提交
    if ( isset( $_POST['wporg_nonce'] ) && wp_verify_nonce( $_POST['wporg_nonce'], 'wporg_save_settings' ) ) {
        $api_key = sanitize_text_field( $_POST['wporg_api_key'] ?? '' );
        update_option( 'wporg_api_key', $api_key );
        echo '<div class="notice notice-success"><p>Settings saved.</p></div>';
    }

    $current_key = get_option( 'wporg_api_key', '' );
    ?>
    <div class="wrap">
        <h1><?php echo esc_html( get_admin_page_title() ); ?></h1>
        <form method="post">
            <?php wp_nonce_field( 'wporg_save_settings', 'wporg_nonce' ); ?>
            <table class="form-table">
                <tr>
                    <th scope="row">
                        <label for="wporg_api_key">API Key</label>
                    </th>
                    <td>
                        <input type="text" id="wporg_api_key" name="wporg_api_key"
                               value="<?php echo esc_attr( $current_key ); ?>"
                               class="regular-text">
                    </td>
                </tr>
            </table>
            <?php submit_button(); ?>
        </form>
    </div>
    <?php
}

在自行處理表單時,務必使用 wp_nonce_field() 產生 nonce 欄位,並在提交時以 wp_verify_nonce() 驗證,以防止 CSRF 攻擊。同時,所有使用者輸入的資料都必須經過適當的清理(sanitize)與跳脫(escape)處理。關於外掛安全性的詳細說明,請參考外掛開發安全性指南

作者實務經驗分享:

1. 優先使用子選單而非頂層選單:除非你的外掛功能非常龐大,需要多個管理頁面,否則建議將設定頁面作為子選單加入到「設定」或「工具」選單下。每個外掛都建立自己的頂層選單,會讓後台左側選單變得又長又亂,對使用者體驗是一大傷害。

2. 善用 Dashicons:如果你確實需要頂層選單,建議使用 WordPress 內建的 Dashicons 作為圖示。Dashicons 不需要載入額外的圖片資源,渲染效能最好,而且風格與 WordPress 後台一致。選擇圖示時,挑選最能代表你外掛功能的圖示,避免使用過於常見的齒輪圖示(dashicons-admin-generic),以免與其他外掛混淆。

3. 權限控制不可省略:在管理頁面的回呼函式中,一定要加上 current_user_can() 的權限檢查。即使選單本身已經設定了 capability 參數,在頁面輸出函式中再做一次驗證是良好的防禦性程式設計。此外,處理表單提交時,nonce 驗證也是不可缺少的安全措施。更多安全性相關的實務建議,請參考外掛開發安全性指南

4. 選單 slug 命名規範:建議使用外掛名稱作為選單 slug 的前綴,例如 my-plugin-settings,以避免與其他外掛或 WordPress 核心的 slug 衝突。slug 應只包含小寫英文字母、數字和連字號。

5. 搭配 Hooks 使用:管理選單的註冊必須透過 admin_menu 這個 Action Hook 來進行,而設定欄位的註冊則使用 admin_init。如果你對 WordPress Hook 機制還不夠熟悉,建議先閱讀 Hooks 的運作機制這篇文章。


本文是「WordPress 外掛開發完整指南」系列的第 5 篇。

上一篇:[WordPress] 外掛開發安全性指南 - 上架外掛前必須通過的考驗

下一篇:[WordPress] Settings API 教學 - 外掛設定頁面開發完整指南


Share:

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *


文章
Filter
Apply Filters
Mastodon