本篇文章更新時間:2026/03/03
如有資訊過時或語誤之處,歡迎使用 Contact 功能通知或向一介資男的 LINE 社群反應。
如果本站內容對你有幫助,歡迎贊助支持 。
本系列文參考自 WordPress.org 官方外掛開發文件 - Metadata 的繁體中文版本,並加入作者實務開發經驗補充。
WordPress Metadata 是外掛開發中不可或缺的資料儲存機制。每篇文章、每位使用者、每個分類項目都可以附加額外的結構化資料,這些資料就稱為 Metadata(後設資料)。透過 WordPress 內建的 Meta API,開發者可以輕鬆地對文章附加自訂欄位;而搭配 Custom Meta Box 開發,則能在文章編輯畫面中建立直覺的資料輸入介面,讓網站管理者無需接觸程式碼就能管理這些附加資料。本文將從 Post Meta 的基礎 CRUD 操作開始,逐步介紹如何建立自訂 Meta Box 並安全地儲存資料。
Post Meta 基礎
Post Meta(文章後設資料)儲存在 WordPress 資料庫的 wp_postmeta 資料表中,每筆記錄包含四個欄位:meta_id(自動遞增主鍵)、post_id(關聯的文章 ID)、meta_key(鍵名)和 meta_value(值)。一篇文章可以擁有多筆相同 meta_key 的記錄,這讓你能夠以陣列的概念儲存一對多的關聯資料。
WordPress 提供了四個核心函式來操作 Post Meta,對應資料庫的 CRUD(建立、讀取、更新、刪除)操作:
新增 Meta - add_post_meta()
add_post_meta() 用於為文章新增一筆後設資料。它接受四個參數:文章 ID、meta key、meta value,以及一個選擇性的 $unique 布林值參數。
/**
* add_post_meta( $post_id, $meta_key, $meta_value, $unique )
*
* @param int $post_id 文章 ID
* @param string $meta_key Meta 鍵名
* @param mixed $meta_value Meta 值(會自動序列化陣列和物件)
* @param bool $unique 是否限制此 key 只能有一筆記錄(預設 false)
* @return int|false 成功時回傳 meta_id,失敗回傳 false
*/
// 基本用法:為文章 ID 42 新增一筆 meta
add_post_meta( 42, 'myplugin_rating', 5 );
// 同一個 key 可以新增多筆記錄(預設行為)
add_post_meta( 42, 'myplugin_rating', 3 );
add_post_meta( 42, 'myplugin_rating', 4 );
// 此時 get_post_meta( 42, 'myplugin_rating', false ) 會回傳 array( 5, 3, 4 )
// 設定 $unique = true,確保同一個 key 只有一筆記錄
// 如果已存在,則不會新增,回傳 false
add_post_meta( 42, 'myplugin_featured', 'yes', true );
當 $meta_value 是陣列或物件時,WordPress 會自動使用 maybe_serialize() 進行序列化後儲存。讀取時則會自動反序列化。
讀取 Meta - get_post_meta()
get_post_meta() 用於讀取文章的後設資料。第三個參數 $single 決定了回傳值的形式:
/**
* get_post_meta( $post_id, $meta_key, $single )
*
* @param int $post_id 文章 ID
* @param string $meta_key Meta 鍵名(空字串則取得所有 meta)
* @param bool $single true 回傳單一值,false 回傳陣列
* @return mixed 單一值或陣列
*/
// 取得單一值($single = true)
$rating = get_post_meta( 42, 'myplugin_rating', true );
// 回傳:5(第一筆記錄的值)
// 取得所有同名 key 的值($single = false)
$all_ratings = get_post_meta( 42, 'myplugin_rating', false );
// 回傳:array( '5', '3', '4' )
// 取得文章的所有 meta(不指定 key)
$all_meta = get_post_meta( 42 );
// 回傳:array( 'myplugin_rating' => array( '5', '3', '4' ), ... )
特別注意:當指定的 $meta_key 不存在時,$single = true 會回傳空字串 '',而 $single = false 會回傳空陣列 array()。這個差異在條件判斷時需要留意。
更新 Meta - update_post_meta()
update_post_meta() 用於更新既有的後設資料。如果指定的 meta_key 不存在,它會自動新增一筆記錄(行為類似 add_post_meta() 搭配 $unique = true)。
/**
* update_post_meta( $post_id, $meta_key, $meta_value, $prev_value )
*
* @param int $post_id 文章 ID
* @param string $meta_key Meta 鍵名
* @param mixed $meta_value 新的 Meta 值
* @param mixed $prev_value 可選,指定要更新哪一筆舊值(用於同 key 多筆記錄的情況)
* @return int|bool 成功回傳 meta_id,值未改變回傳 false
*/
// 更新 meta 值
update_post_meta( 42, 'myplugin_rating', 8 );
// 如果有多筆同名 key,可以指定要更新哪一筆
update_post_meta( 42, 'myplugin_rating', 10, 3 );
// 將值為 3 的那筆記錄更新為 10
// 如果 key 不存在,update_post_meta 會自動新增
update_post_meta( 42, 'myplugin_new_field', 'hello' );
由於 update_post_meta() 在 key 不存在時會自動新增,在實務開發中,如果你的需求是「一個 key 只存一個值」,通常直接使用 update_post_meta() 就足夠了,不需要先檢查是否存在再決定要用 add 還是 update。
刪除 Meta - delete_post_meta()
delete_post_meta() 用於刪除文章的後設資料。可以選擇性地指定要刪除的特定值,這在同一個 key 有多筆記錄時特別有用。
/**
* delete_post_meta( $post_id, $meta_key, $meta_value )
*
* @param int $post_id 文章 ID
* @param string $meta_key Meta 鍵名
* @param mixed $meta_value 可選,指定要刪除的特定值
* @return bool 成功回傳 true,失敗回傳 false
*/
// 刪除所有同名 key 的記錄
delete_post_meta( 42, 'myplugin_rating' );
// 只刪除值為 3 的那筆記錄
delete_post_meta( 42, 'myplugin_rating', 3 );
在外掛停用或移除時,記得清理外掛所建立的 meta 資料。可以在 register_uninstall_hook() 的回呼函式中執行批量刪除。
Meta 值的資料型別
WordPress 的 Meta API 支援儲存各種 PHP 資料型別,但在讀取時需要注意型別轉換的問題:
// 儲存不同類型的資料
update_post_meta( 42, 'myplugin_count', 100 ); // 整數
update_post_meta( 42, 'myplugin_price', 29.99 ); // 浮點數
update_post_meta( 42, 'myplugin_active', true ); // 布林值
update_post_meta( 42, 'myplugin_tags', array( 'php', 'wordpress' ) ); // 陣列
// 讀取時,所有值都會以字串形式回傳
$count = get_post_meta( 42, 'myplugin_count', true );
// $count 的值是 '100'(字串),不是 100(整數)
// 因此在使用前需要進行型別轉換
$count = (int) get_post_meta( 42, 'myplugin_count', true );
$price = (float) get_post_meta( 42, 'myplugin_price', true );
$active = filter_var( get_post_meta( 42, 'myplugin_active', true ), FILTER_VALIDATE_BOOLEAN );
// 陣列和物件會自動序列化/反序列化,不需要手動處理
$tags = get_post_meta( 42, 'myplugin_tags', true );
// $tags 的值是 array( 'php', 'wordpress' )
Custom Meta Boxes
Meta Box(後設資料框)是 WordPress 後台編輯畫面中的可拖曳區塊。每個 Meta Box 提供一個獨立的介面區域,讓使用者能夠輸入或檢視與文章相關的附加資料。WordPress 後台預設就有許多 Meta Box,例如「發佈」、「分類」、「標籤」等。透過外掛開發,你可以註冊自訂的 Meta Box 來擴展編輯介面。
使用 add_meta_box() 註冊 Meta Box
add_meta_box() 是註冊自訂 Meta Box 的核心函式,必須在 add_meta_boxes 動作鉤子中呼叫:
/**
* add_meta_box( $id, $title, $callback, $screen, $context, $priority, $callback_args )
*
* @param string $id Meta Box 的唯一識別 ID(也作為 HTML 的 id 屬性)
* @param string $title Meta Box 的標題,顯示在框的標題列
* @param callable $callback 渲染 Meta Box 內容的回呼函式
* @param string $screen 顯示在哪個編輯畫面(post type 名稱,如 'post'、'page')
* @param string $context Meta Box 的位置:'normal'、'side'、'advanced'
* @param string $priority 顯示優先順序:'high'、'core'、'default'、'low'
* @param array $callback_args 傳遞給回呼函式的額外參數
*/
以下是一個完整的 Meta Box 註冊範例,建立一個讓使用者輸入文章額外資訊的介面:
/**
* 註冊自訂 Meta Box
*
* 使用 add_meta_boxes 鉤子來註冊 Meta Box,
* 這個鉤子會在文章編輯畫面載入時觸發。
*/
function myplugin_register_meta_boxes() {
add_meta_box(
'myplugin_post_options', // $id:唯一識別 ID
'文章附加選項', // $title:顯示標題
'myplugin_render_meta_box', // $callback:渲染函式
'post', // $screen:顯示在文章編輯畫面
'normal', // $context:主要內容區域
'high' // $priority:高優先順序
);
}
add_action( 'add_meta_boxes', 'myplugin_register_meta_boxes' );
/**
* 渲染 Meta Box 的 HTML 內容
*
* @param WP_Post $post 目前正在編輯的文章物件
*/
function myplugin_render_meta_box( $post ) {
// 讀取已儲存的 meta 值
$subtitle = get_post_meta( $post->ID, '_myplugin_subtitle', true );
$source_url = get_post_meta( $post->ID, '_myplugin_source_url', true );
$is_featured = get_post_meta( $post->ID, '_myplugin_is_featured', true );
// 產生 nonce 欄位用於安全驗證
wp_nonce_field( 'myplugin_save_meta', 'myplugin_meta_nonce' );
?>
<table class="form-table">
<tr>
<th><label for="myplugin-subtitle">副標題</label></th>
<td>
<input type="text"
id="myplugin-subtitle"
name="myplugin_subtitle"
value="<?php echo esc_attr( $subtitle ); ?>"
class="regular-text">
<p class="description">顯示在文章標題下方的副標題文字。</p>
</td>
</tr>
<tr>
<th><label for="myplugin-source-url">資料來源網址</label></th>
<td>
<input type="url"
id="myplugin-source-url"
name="myplugin_source_url"
value="<?php echo esc_url( $source_url ); ?>"
class="regular-text">
</td>
</tr>
<tr>
<th>精選文章</th>
<td>
<label>
<input type="checkbox"
name="myplugin_is_featured"
value="1"
<?php checked( $is_featured, '1' ); ?>>
將此文章標記為精選文章
</label>
</td>
</tr>
</table>
<?php
}
Meta Box 的位置與優先順序
$context 參數決定 Meta Box 出現的位置:
'normal':在內容編輯器下方的主要區域'side':在右側邊欄(與「發佈」、「分類」等 Meta Box 同一區域)'advanced':在 normal 區域之後,與 normal 類似但排序較後
$priority 參數決定同一位置中的排列順序:
'high':排在最前面'core':WordPress 核心 Meta Box 的預設優先順序'default':一般優先順序'low':排在最後面
你也可以在多個 post type 上註冊同一個 Meta Box:
function myplugin_register_meta_boxes() {
// 在文章和頁面編輯畫面都顯示
$screens = array( 'post', 'page' );
foreach ( $screens as $screen ) {
add_meta_box(
'myplugin_post_options',
'附加選項',
'myplugin_render_meta_box',
$screen,
'side',
'default'
);
}
}
add_action( 'add_meta_boxes', 'myplugin_register_meta_boxes' );
移除 Meta Box
如果你需要移除 WordPress 預設的 Meta Box 或其他外掛所註冊的 Meta Box,可以使用 remove_meta_box():
function myplugin_remove_meta_boxes() {
// 移除文章編輯畫面的「自訂欄位」Meta Box
remove_meta_box( 'postcustom', 'post', 'normal' );
// 移除文章編輯畫面的「摘要」Meta Box
remove_meta_box( 'postexcerpt', 'post', 'normal' );
}
add_action( 'add_meta_boxes', 'myplugin_remove_meta_boxes' );
儲存 Meta Box 資料
Meta Box 只負責顯示介面,資料的儲存需要透過 save_post 動作鉤子來處理。在儲存過程中,安全性驗證是不可忽略的步驟,包括 nonce 驗證、權限檢查和自動儲存的處理。
完整的儲存流程
/**
* 儲存 Meta Box 的資料
*
* 透過 save_post 鉤子在文章儲存時執行。
* 必須進行完整的安全性驗證,確保資料來自合法的表單提交。
*
* @param int $post_id 正在儲存的文章 ID
*/
function myplugin_save_meta_box_data( $post_id ) {
// 1. 驗證 nonce(防止 CSRF 攻擊)
if ( ! isset( $_POST['myplugin_meta_nonce'] ) ) {
return;
}
if ( ! wp_verify_nonce( $_POST['myplugin_meta_nonce'], 'myplugin_save_meta' ) ) {
return;
}
// 2. 檢查是否為自動儲存(Auto Save)
// 自動儲存時不處理 meta box 的資料,避免覆蓋使用者正在編輯的內容
if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
return;
}
// 3. 檢查使用者權限
if ( isset( $_POST['post_type'] ) && 'page' === $_POST['post_type'] ) {
if ( ! current_user_can( 'edit_page', $post_id ) ) {
return;
}
} else {
if ( ! current_user_can( 'edit_post', $post_id ) ) {
return;
}
}
// 4. 清理(Sanitize)並儲存資料
// 文字欄位:使用 sanitize_text_field() 清理
if ( isset( $_POST['myplugin_subtitle'] ) ) {
$subtitle = sanitize_text_field( $_POST['myplugin_subtitle'] );
update_post_meta( $post_id, '_myplugin_subtitle', $subtitle );
}
// URL 欄位:使用 esc_url_raw() 清理
if ( isset( $_POST['myplugin_source_url'] ) ) {
$source_url = esc_url_raw( $_POST['myplugin_source_url'] );
update_post_meta( $post_id, '_myplugin_source_url', $source_url );
}
// 核取方塊:未勾選時 $_POST 中不會有該欄位
$is_featured = isset( $_POST['myplugin_is_featured'] ) ? '1' : '0';
update_post_meta( $post_id, '_myplugin_is_featured', $is_featured );
}
add_action( 'save_post', 'myplugin_save_meta_box_data' );
儲存流程的安全性檢查說明
上述儲存函式中的每一步安全性檢查都有其必要性:
- Nonce 驗證:Nonce(Number Used Once)是 WordPress 的 CSRF 防護機制。在 Meta Box 的渲染函式中透過
wp_nonce_field()產生一個隱藏的 nonce 欄位,儲存時透過wp_verify_nonce()驗證。這確保了資料是從你的表單提交的,而不是來自惡意的跨站請求。 - 自動儲存檢查:WordPress 的自動儲存功能會定期透過 AJAX 儲存文章內容,但此時 Meta Box 的表單資料可能不完整。跳過自動儲存可以避免意外清空或覆蓋 meta 資料。
- 權限檢查:即使 nonce 驗證通過,仍需確認目前使用者確實有權限編輯該文章。這是縱深防禦(Defense in Depth)策略的一部分。
- 資料清理:所有來自
$_POST的資料都是使用者輸入,必須在儲存前進行適當的清理(Sanitize)。根據資料類型選擇對應的清理函式。
處理複雜的資料結構
當 Meta Box 包含多個相關欄位時,可以將它們組合成一個陣列來儲存,減少資料庫中的 meta 記錄數量:
/**
* 渲染包含多個欄位的 Meta Box
*/
function myplugin_render_advanced_meta_box( $post ) {
$options = get_post_meta( $post->ID, '_myplugin_options', true );
$options = wp_parse_args( $options, array(
'layout' => 'default',
'show_sidebar' => '1',
'custom_css' => '',
) );
wp_nonce_field( 'myplugin_save_options', 'myplugin_options_nonce' );
?>
<p>
<label for="myplugin-layout">頁面佈局:</label>
<select id="myplugin-layout" name="myplugin_options[layout]">
<option value="default" <?php selected( $options['layout'], 'default' ); ?>>預設</option>
<option value="full-width" <?php selected( $options['layout'], 'full-width' ); ?>>全寬</option>
<option value="sidebar-left" <?php selected( $options['layout'], 'sidebar-left' ); ?>>左側欄</option>
</select>
</p>
<p>
<label>
<input type="checkbox"
name="myplugin_options[show_sidebar]"
value="1"
<?php checked( $options['show_sidebar'], '1' ); ?>>
顯示側邊欄
</label>
</p>
<p>
<label for="myplugin-custom-css">自訂 CSS:</label><br>
<textarea id="myplugin-custom-css"
name="myplugin_options[custom_css]"
rows="5"
cols="50"><?php echo esc_textarea( $options['custom_css'] ); ?></textarea>
</p>
<?php
}
/**
* 儲存進階 Meta Box 的資料
*/
function myplugin_save_advanced_meta_box( $post_id ) {
if ( ! isset( $_POST['myplugin_options_nonce'] ) ||
! wp_verify_nonce( $_POST['myplugin_options_nonce'], 'myplugin_save_options' ) ) {
return;
}
if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
return;
}
if ( ! current_user_can( 'edit_post', $post_id ) ) {
return;
}
if ( isset( $_POST['myplugin_options'] ) && is_array( $_POST['myplugin_options'] ) ) {
$raw_options = $_POST['myplugin_options'];
$clean_options = array(
'layout' => sanitize_text_field( $raw_options['layout'] ?? 'default' ),
'show_sidebar' => isset( $raw_options['show_sidebar'] ) ? '1' : '0',
'custom_css' => wp_strip_all_tags( $raw_options['custom_css'] ?? '' ),
);
update_post_meta( $post_id, '_myplugin_options', $clean_options );
}
}
add_action( 'save_post', 'myplugin_save_advanced_meta_box' );
隱藏與受保護的 Meta Key
以底線(_)開頭的 meta key(例如 _myplugin_subtitle)被視為「受保護的」meta key,不會出現在 WordPress 後台的「自訂欄位」面板中。這是一個重要的命名慣例:
- 受保護的 key(底線開頭):
_myplugin_subtitle— 只能透過你的 Meta Box 介面或程式碼存取,不會顯示在預設的自訂欄位面板。建議外掛所使用的 meta key 都採用此命名方式。 - 一般的 key(無底線開頭):
myplugin_subtitle— 會顯示在「自訂欄位」面板中,使用者可以直接編輯。
你可以透過 is_protected_meta 過濾器來自訂哪些 meta key 需要被保護:
/**
* 將特定的 meta key 標記為受保護
*/
function myplugin_protect_meta_keys( $protected, $meta_key ) {
if ( 'myplugin_special_data' === $meta_key ) {
return true;
}
return $protected;
}
add_filter( 'is_protected_meta', 'myplugin_protect_meta_keys', 10, 2 );
實務建議
作者實務經驗分享:
1. Meta Key 命名規範:一律以你的外掛名稱作為前綴,例如
_myplugin_field_name。這不僅能避免與其他外掛衝突,也讓你在排查資料庫問題時能快速識別哪些 meta 記錄屬於你的外掛。我在實務開發中看過許多因為使用title、color這類通用名稱而導致的衝突問題,debug 起來非常痛苦。另外,使用底線開頭(如_myplugin_)可以防止這些欄位出現在後台預設的「自訂欄位」面板中,避免使用者誤改重要資料。2. Nonce 驗證的重要性:每個 Meta Box 都必須實作 nonce 驗證,這是 WordPress 安全開發的基本要求。省略 nonce 驗證等於是為 CSRF 攻擊敞開大門。關於 WordPress 外掛安全性的完整說明,包括資料清理、輸出跳脫和 nonce 的深入解析,請參考我之前撰寫的「WordPress 外掛開發安全性指南」。
3. 善用 Hooks 整合:
add_meta_boxes和save_post是 Meta Box 開發最核心的兩個鉤子。如果你對 WordPress 的 Hook 機制還不熟悉,建議先閱讀「WordPress Hooks 完整教學」建立基礎觀念。理解 Hooks 的運作原理,才能正確掌握 Meta Box 在編輯流程中的觸發時機。4. Classic Editor vs Block Editor:本文介紹的
add_meta_box()方法在傳統編輯器中會以完整的區塊形式呈現。在 Gutenberg 區塊編輯器中,這些 Meta Box 會被放置在編輯器下方或側邊欄的「外掛」面板中,功能不受影響但視覺呈現有所不同。對於更深度整合 Gutenberg 的需求,可以考慮使用register_post_meta()搭配 Block Editor 的PluginDocumentSettingPanel元件。5. 效能考量:WordPress 在載入文章時會一次性把所有 post meta 快取起來,所以多次呼叫
get_post_meta()不會重複查詢資料庫。但如果你在一個迴圈中載入大量文章的 meta 資料(例如文章列表頁面),建議使用update_post_meta_cache()預先批量載入,避免 N+1 查詢問題。
本文是「WordPress 外掛開發完整指南」系列的第 8 篇。
上一篇:[WordPress] Shortcodes 短碼開發教學 - 建立自訂內容功能
下一篇:[WordPress] 自訂內容類型(CPT)與分類法開發教學
