本篇文章更新時間:2026/03/03
如有資訊過時或語誤之處,歡迎使用 Contact 功能通知或向一介資男的 LINE 社群反應。
如果本站內容對你有幫助,歡迎贊助支持 。
本系列文參考自 WordPress.org 官方外掛開發文件 - Users 的繁體中文版本,並加入作者實務開發經驗補充。
WordPress 角色權限系統是控制使用者存取與操作的核心安全機制。WordPress 透過「角色(Roles)」和「權限(Capabilities)」的組合,精確管理每位使用者能做什麼、不能做什麼。在外掛開發中,我們經常需要操作使用者的自訂資料(User Metadata)、檢查使用者權限、甚至建立全新的角色來滿足特殊的業務需求——例如「店長」、「審核者」、「VIP 會員」等。本文將從 User Metadata 的 CRUD 操作開始,逐步介紹 WordPress 內建角色與權限體系,並說明如何安全地自訂角色與權限。
使用者 Metadata
WordPress 的使用者資料主要儲存在兩張資料表中:wp_users 儲存帳號、信箱、密碼等核心欄位,而 wp_usermeta 則以 key-value 形式儲存額外的使用者資料。這個設計與 Post Meta 的概念完全相同——透過 Metadata API,開發者可以為每位使用者附加任意數量的自訂資料,而不需要修改資料庫結構。
常見的 User Metadata 使用場景包括:
- 儲存使用者的偏好設定(如後台介面語言、通知偏好)
- 記錄使用者的額外個人資料(如社群帳號連結、自我介紹)
- 追蹤使用者的操作紀錄(如最後登入時間、瀏覽歷史)
- 儲存外掛功能需要的使用者層級資料(如 API key、訂閱狀態)
新增 User Metadata - add_user_meta()
使用 add_user_meta() 為使用者新增 metadata。此函式的第四個參數 $unique 決定是否允許重複的 key:
/**
* 新增使用者 metadata
*
* @param int $user_id 使用者 ID
* @param string $meta_key metadata 名稱
* @param mixed $meta_value metadata 值
* @param bool $unique 是否唯一(預設 false,允許多筆同 key 資料)
* @return int|false 新增的 meta_id 或失敗時回傳 false
*/
add_user_meta( $user_id, $meta_key, $meta_value, $unique );
// 範例:記錄使用者的社群帳號連結
add_user_meta( $user_id, 'myplugin_twitter_url', 'https://twitter.com/example', true );
// 範例:允許一位使用者儲存多筆技能標籤
add_user_meta( $user_id, 'myplugin_skill', 'PHP' );
add_user_meta( $user_id, 'myplugin_skill', 'JavaScript' );
add_user_meta( $user_id, 'myplugin_skill', 'WordPress' );
注意:如果 $unique 設為 true,當指定的 key 已存在時,add_user_meta() 不會新增也不會更新,而是直接回傳 false。這和 update_user_meta() 的行為不同。
讀取 User Metadata - get_user_meta()
使用 get_user_meta() 讀取使用者的 metadata 值:
/**
* 讀取使用者 metadata
*
* @param int $user_id 使用者 ID
* @param string $meta_key metadata 名稱(留空取得全部)
* @param bool $single 是否回傳單一值(預設 false)
* @return mixed 根據參數回傳不同格式
*/
get_user_meta( $user_id, $meta_key, $single );
// 範例一:取得單一值($single = true)
$twitter_url = get_user_meta( $user_id, 'myplugin_twitter_url', true );
// 回傳:'https://twitter.com/example'(字串)
// 範例二:取得該 key 的所有值($single = false)
$skills = get_user_meta( $user_id, 'myplugin_skill', false );
// 回傳:array( 'PHP', 'JavaScript', 'WordPress' )
// 範例三:取得使用者的所有 metadata
$all_meta = get_user_meta( $user_id );
// 回傳:array( 'myplugin_twitter_url' => array('https://...'), 'myplugin_skill' => array('PHP', 'JS', 'WP'), ... )
重要:$single 參數的差異是新手常見的踩坑點。當 $single 為 true 時,回傳的是 metadata 的實際值(字串、陣列等);當 $single 為 false(預設)時,回傳的是包含所有該 key 值的陣列。如果你只需要一筆資料,記得傳入 true。
更新 User Metadata - update_user_meta()
使用 update_user_meta() 更新使用者的 metadata。如果指定的 key 不存在,此函式會自動建立:
/**
* 更新使用者 metadata
*
* @param int $user_id 使用者 ID
* @param string $meta_key metadata 名稱
* @param mixed $meta_value 新的 metadata 值
* @param mixed $prev_value 選填,指定要更新的舊值(用於同一 key 有多筆值時)
* @return int|bool 成功時回傳 meta_id,值未變更時回傳 false
*/
update_user_meta( $user_id, $meta_key, $meta_value, $prev_value );
// 範例一:更新社群帳號
update_user_meta( $user_id, 'myplugin_twitter_url', 'https://twitter.com/new_handle' );
// 範例二:如果 key 不存在,update_user_meta() 會自動新增
update_user_meta( $user_id, 'myplugin_last_login', current_time( 'mysql' ) );
// 範例三:利用 $prev_value 精確更新特定值
update_user_meta( $user_id, 'myplugin_skill', 'TypeScript', 'JavaScript' );
刪除 User Metadata - delete_user_meta()
使用 delete_user_meta() 刪除使用者的 metadata:
/**
* 刪除使用者 metadata
*
* @param int $user_id 使用者 ID
* @param string $meta_key metadata 名稱
* @param mixed $meta_value 選填,只刪除符合此值的 metadata
* @return bool 成功回傳 true,失敗回傳 false
*/
delete_user_meta( $user_id, $meta_key, $meta_value );
// 範例一:刪除使用者的所有 myplugin_twitter_url 資料
delete_user_meta( $user_id, 'myplugin_twitter_url' );
// 範例二:只刪除特定值(例如移除某一項技能)
delete_user_meta( $user_id, 'myplugin_skill', 'PHP' );
User Metadata 的安全性考量
操作 User Metadata 時,請務必注意以下安全原則:
/**
* 安全地更新使用者 metadata 的完整範例
*/
function myplugin_save_user_profile( $user_id ) {
// 1. 檢查權限:確認目前使用者有權編輯此使用者資料
if ( ! current_user_can( 'edit_user', $user_id ) ) {
return false;
}
// 2. 驗證 nonce:防止 CSRF 攻擊
if ( ! wp_verify_nonce( $_POST['myplugin_user_nonce'], 'myplugin_save_user_profile' ) ) {
return false;
}
// 3. 清理輸入資料
if ( isset( $_POST['myplugin_twitter_url'] ) ) {
$twitter_url = esc_url_raw( $_POST['myplugin_twitter_url'] );
update_user_meta( $user_id, 'myplugin_twitter_url', $twitter_url );
}
if ( isset( $_POST['myplugin_bio'] ) ) {
$bio = sanitize_textarea_field( $_POST['myplugin_bio'] );
update_user_meta( $user_id, 'myplugin_bio', $bio );
}
}
add_action( 'personal_options_update', 'myplugin_save_user_profile' );
add_action( 'edit_user_profile_update', 'myplugin_save_user_profile' );
在 User Metadata 的前綴命名方面,建議統一使用外掛專屬的前綴(如 myplugin_),避免與其他外掛或佈景主題發生衝突。關於更完整的資料驗證與清理方法,請參考「WordPress 安全性完整教學」。
角色與權限系統(Roles & Capabilities)
WordPress 的存取控制採用「角色(Roles)」和「權限(Capabilities)」的兩層架構。每個角色是一組權限的集合,而每位使用者可以被指派一個角色。角色和權限資料儲存在 wp_options 資料表的 wp_user_roles 選項中,而使用者所屬的角色則儲存在 wp_usermeta 資料表的 wp_capabilities 欄位。
WordPress 內建角色一覽
WordPress 預設提供以下六種角色,從最高權限到最低權限排列:
| 角色 | 識別名稱 | 說明 | 主要權限 |
|---|---|---|---|
| 超級管理員 | super_admin |
僅存在於多站網路(Multisite),可管理整個網路 | 所有權限 + 網路管理 |
| 管理員 | administrator |
單站的最高權限角色 | 所有單站權限 |
| 編輯 | editor |
可管理所有文章與頁面的內容 | publish_pages, edit_others_posts, manage_categories 等 |
| 作者 | author |
可發佈與管理自己的文章 | publish_posts, edit_published_posts, upload_files 等 |
| 投稿者 | contributor |
可撰寫文章,但不能發佈 | edit_posts, read 等(不含 upload_files) |
| 訂閱者 | subscriber |
只能管理自己的個人資料 | read |
每個角色擁有一組預定義的權限。WordPress 的權限大致分為以下幾類:
- 文章相關:
edit_posts、publish_posts、edit_others_posts、delete_posts等 - 頁面相關:
edit_pages、publish_pages、edit_others_pages、delete_pages等 - 使用者相關:
list_users、create_users、edit_users、delete_users、promote_users - 外掛與佈景主題:
activate_plugins、install_plugins、switch_themes、edit_themes - 系統管理:
manage_options、export、import、update_core
使用 current_user_can() 檢查權限
current_user_can() 是外掛開發中最常使用的權限檢查函式,它可以判斷目前登入的使用者是否擁有指定的權限:
/**
* 檢查目前使用者是否擁有特定權限
*
* @param string $capability 權限名稱
* @param mixed ...$args 額外參數(用於物件層級的權限檢查)
* @return bool 使用者是否擁有該權限
*/
current_user_can( $capability, ...$args );
// 範例一:基礎權限檢查
if ( current_user_can( 'manage_options' ) ) {
// 只有管理員可以看到這段內容
echo '<p>網站管理設定連結</p>';
}
// 範例二:在後台選單中使用權限控制
add_menu_page(
'外掛設定',
'我的外掛',
'manage_options', // 只有擁有此權限的使用者才能看到選單
'myplugin-settings',
'myplugin_settings_page'
);
// 範例三:在 AJAX 處理中檢查權限
function myplugin_ajax_delete_item() {
// 檢查使用者是否有刪除權限
if ( ! current_user_can( 'delete_posts' ) ) {
wp_send_json_error( '權限不足', 403 );
}
// 驗證 nonce
check_ajax_referer( 'myplugin_nonce', 'security' );
// 執行刪除操作...
wp_send_json_success( '刪除成功' );
}
add_action( 'wp_ajax_myplugin_delete_item', 'myplugin_ajax_delete_item' );
重要原則:在外掛開發中,應該以「權限(Capability)」而非「角色(Role)」來做存取控制。例如,使用 current_user_can( 'manage_options' ) 而非 current_user_can( 'administrator' )。雖然後者也能運作,但前者更靈活——因為管理員可以將特定權限授予其他角色,而以角色做判斷就無法適應這種彈性配置。
user_can() 與 Meta Capabilities
除了 current_user_can(),WordPress 還提供 user_can() 來檢查指定使用者的權限:
// 檢查特定使用者(而非目前使用者)的權限
$can_edit = user_can( $user_id, 'edit_posts' );
// Meta Capabilities - 物件層級的權限檢查
// 檢查目前使用者是否能編輯「這篇」特定文章
if ( current_user_can( 'edit_post', $post_id ) ) {
// 注意:'edit_post'(單數)是 meta capability
// WordPress 會自動將它映射到 'edit_posts' 或 'edit_others_posts'
// 取決於文章是否為該使用者所撰寫
}
// 檢查目前使用者是否能刪除「這位」特定使用者
if ( current_user_can( 'delete_user', $target_user_id ) ) {
// 'delete_user' 是 meta capability
// WordPress 會檢查目標使用者是否為管理員等條件
}
Meta Capabilities(元權限)是 WordPress 權限系統的進階機制。它們不是直接被角色持有的權限,而是 WordPress 在檢查時透過 map_meta_cap 過濾器動態映射到實際的「原始權限(Primitive Capabilities)」。例如 edit_post(編輯某一篇文章)會根據文章擁有者和使用者身份,映射到 edit_posts 或 edit_others_posts。
自訂角色與權限
當 WordPress 內建的角色無法滿足專案需求時,外掛可以建立自訂角色或為現有角色新增自訂權限。這在許多商業應用中非常常見——例如 WooCommerce 新增了「商店管理員(Shop Manager)」和「客戶(Customer)」兩個角色。
新增角色 - add_role()
/**
* 新增自訂角色
*
* @param string $role 角色識別名稱(小寫英文與底線)
* @param string $display_name 角色顯示名稱
* @param array $capabilities 該角色擁有的權限陣列
* @return WP_Role|null 成功回傳 WP_Role 物件,角色已存在則回傳 null
*/
add_role( $role, $display_name, $capabilities );
// 範例:建立「內容審核者」角色
add_role(
'content_reviewer',
'內容審核者',
array(
'read' => true, // 可閱讀
'edit_posts' => true, // 可編輯文章
'edit_others_posts' => true, // 可編輯他人文章
'edit_published_posts' => true, // 可編輯已發佈文章
'publish_posts' => true, // 可發佈文章
'delete_posts' => false, // 不可刪除文章
'manage_categories' => false, // 不可管理分類
)
);
移除角色 - remove_role()
/**
* 移除自訂角色
*
* @param string $role 角色識別名稱
*/
remove_role( $role );
// 範例:移除先前建立的角色
remove_role( 'content_reviewer' );
注意:移除角色前,請確保沒有使用者正在使用該角色。如果有使用者被指派了即將移除的角色,這些使用者將失去所有權限。建議在移除前先將這些使用者轉移到其他角色。
為現有角色新增或移除權限
除了建立全新角色,你也可以為現有角色新增自訂權限:
// 取得角色物件
$editor_role = get_role( 'editor' );
// 為「編輯」角色新增自訂權限
if ( $editor_role ) {
$editor_role->add_cap( 'myplugin_manage_settings' );
$editor_role->add_cap( 'myplugin_view_reports' );
}
// 移除角色的某項權限
if ( $editor_role ) {
$editor_role->remove_cap( 'myplugin_manage_settings' );
}
關鍵:只在外掛啟用時執行角色與權限操作
這是外掛開發中極為重要的觀念:角色和權限資料儲存在資料庫中(wp_options 資料表的 wp_user_roles 選項),而非在 PHP 程式碼中即時產生。因此,add_role() 和 add_cap() 只需要執行一次,就會永久寫入資料庫。如果你把這些操作放在 init Hook 中,每次頁面載入都會觸發資料庫寫入,嚴重影響效能。
正確做法:將角色和權限操作放在外掛啟用鉤子(activation hook)中執行,並在外掛停用時清理:
/**
* 外掛啟用時建立角色與權限
*/
function myplugin_activate() {
// 建立自訂角色
add_role(
'content_reviewer',
'內容審核者',
array(
'read' => true,
'edit_posts' => true,
'edit_others_posts' => true,
'edit_published_posts' => true,
'publish_posts' => true,
)
);
// 為既有角色新增自訂權限
$admin_role = get_role( 'administrator' );
if ( $admin_role ) {
$admin_role->add_cap( 'myplugin_manage_settings' );
$admin_role->add_cap( 'myplugin_view_reports' );
$admin_role->add_cap( 'myplugin_manage_reviews' );
}
$editor_role = get_role( 'editor' );
if ( $editor_role ) {
$editor_role->add_cap( 'myplugin_view_reports' );
$editor_role->add_cap( 'myplugin_manage_reviews' );
}
}
register_activation_hook( __FILE__, 'myplugin_activate' );
/**
* 外掛停用時清理角色與權限
*/
function myplugin_deactivate() {
// 移除自訂角色
remove_role( 'content_reviewer' );
// 移除為既有角色新增的自訂權限
$admin_role = get_role( 'administrator' );
if ( $admin_role ) {
$admin_role->remove_cap( 'myplugin_manage_settings' );
$admin_role->remove_cap( 'myplugin_view_reports' );
$admin_role->remove_cap( 'myplugin_manage_reviews' );
}
$editor_role = get_role( 'editor' );
if ( $editor_role ) {
$editor_role->remove_cap( 'myplugin_view_reports' );
$editor_role->remove_cap( 'myplugin_manage_reviews' );
}
}
register_deactivation_hook( __FILE__, 'myplugin_deactivate' );
自訂權限的實際應用
以下是一個完整的範例,展示如何在外掛中使用自訂權限來控制功能的存取:
/**
* 外掛設定頁面 - 使用自訂權限控制存取
*/
function myplugin_add_admin_menu() {
// 主選單 - 需要 myplugin_manage_settings 權限
add_menu_page(
'外掛設定',
'我的外掛',
'myplugin_manage_settings',
'myplugin-settings',
'myplugin_settings_page',
'dashicons-admin-generic',
30
);
// 報告子選單 - 需要 myplugin_view_reports 權限
add_submenu_page(
'myplugin-settings',
'報告',
'報告',
'myplugin_view_reports',
'myplugin-reports',
'myplugin_reports_page'
);
// 審核子選單 - 需要 myplugin_manage_reviews 權限
add_submenu_page(
'myplugin-settings',
'內容審核',
'內容審核',
'myplugin_manage_reviews',
'myplugin-reviews',
'myplugin_reviews_page'
);
}
add_action( 'admin_menu', 'myplugin_add_admin_menu' );
/**
* 在前台也使用自訂權限控制內容顯示
*/
function myplugin_restricted_content( $content ) {
if ( is_singular( 'premium_content' ) ) {
if ( ! current_user_can( 'myplugin_view_premium' ) ) {
return '<p>此內容僅限授權使用者閱覽。</p>';
}
}
return $content;
}
add_filter( 'the_content', 'myplugin_restricted_content' );
處理外掛更新時的角色變更
當外掛更新需要新增權限或修改角色時,由於啟用鉤子不會在外掛更新時觸發,你需要一個版本檢查機制:
/**
* 版本檢查機制 - 處理外掛更新時的角色與權限變更
*/
function myplugin_check_version() {
$current_version = '2.0.0';
$installed_version = get_option( 'myplugin_version', '0.0.0' );
if ( version_compare( $installed_version, $current_version, '<' ) ) {
// 執行升級邏輯
myplugin_upgrade_roles( $installed_version );
update_option( 'myplugin_version', $current_version );
}
}
add_action( 'admin_init', 'myplugin_check_version' );
/**
* 根據版本差異執行對應的角色更新
*/
function myplugin_upgrade_roles( $from_version ) {
// 1.0.0 → 2.0.0:新增「報告」權限
if ( version_compare( $from_version, '2.0.0', '<' ) ) {
$admin_role = get_role( 'administrator' );
if ( $admin_role ) {
$admin_role->add_cap( 'myplugin_view_reports' );
}
}
}
這種版本檢查的寫法確保了每次升級操作只執行一次。由於放在 admin_init 中,只有在後台管理頁面載入時才會觸發版本比對,而不會影響前台效能。
實務建議
作者實務經驗分享:
1. 永遠以權限而非角色做判斷:這是 WordPress 官方文件反覆強調的原則。使用
current_user_can( 'manage_options' )而非current_user_can( 'administrator' )。以角色做判斷會讓你的外掛失去彈性——例如在多站網路中,manage_options權限的分配可能與單站不同。以權限做判斷才能讓網站管理員靈活地透過角色管理外掛配置。2. 自訂權限命名一律加前綴:和 Post Meta、User Meta 一樣,自訂權限的命名也應該加上外掛前綴,例如
myplugin_manage_settings。這不僅避免命名衝突,也讓其他開發者一眼就能看出這個權限來自哪個外掛。3. 角色操作務必放在啟用鉤子:我在 code review 中最常看到的錯誤之一,就是開發者把
add_role()或add_cap()放在initHook 中。這會造成每次頁面載入都寫入資料庫——角色和權限資料儲存在wp_options中,每次呼叫都會觸發UPDATE查詢。正確做法是只在外掛啟用(register_activation_hook)時執行一次。4. 善用 Nonce 搭配權限檢查:權限檢查和 Nonce 驗證是兩道不同的防線。權限檢查確保使用者「有權」執行操作,Nonce 驗證確保請求「來自」正確的頁面。兩者缺一不可,詳細的安全機制說明請參考「WordPress 安全性完整教學」。
5. 測試不同角色的體驗:開發外掛時,別只用管理員帳號測試。建議建立幾個不同角色的測試帳號(編輯、作者、投稿者、訂閱者),實際登入這些帳號確認外掛在不同權限下的表現是否符合預期。特別注意錯誤訊息是否友善、權限不足時是否有適當的提示而非直接顯示空白頁面。
6. 搭配 Hooks 建立完整的權限架構:角色和權限的設定只是第一步。在實務中,你通常還需要搭配
admin_menu控制選單可見性、用map_meta_cap過濾器自訂 meta capability 的映射邏輯、以及透過user_has_cap過濾器動態調整使用者權限。如果你對 WordPress Hook 機制還不熟悉,建議先閱讀「WordPress Hooks 完整教學」建立基礎觀念。
本文是「WordPress 外掛開發完整指南」系列的第 10 篇。
上一篇:[WordPress] 自訂內容類型(CPT)與分類法開發教學
下一篇:[WordPress] HTTP API 教學 - 外掛串接外部資源的正確方式
