多數人以為短代碼是外掛才有的功能,想在文章裡塞一個聯絡表單、嵌入一段影音,第一步就是去外掛市集找現成外掛裝上去。其實短代碼(shortcode)本身是 WordPress 核心內建的一套 API,從 2.5 版就存在,、、 這幾個天天用到的方括號標記,全都不靠任何外掛。
真正的困境是,多數人只會用別人寫好的短代碼,想要一個外掛剛好沒做出來的效果,例如文章裡固定放一顆「立即詢價」按鈕,就只能將就湊合,或是為了一個小功能多裝一支外掛拖慢網站。短代碼指的就是文章內容裡用方括號包住的一段文字,WordPress 輸出文章時會掃描全文,把已註冊的方括號標記換成對應函式的回傳值;自己寫一個並不難,難的是沒人把「怎麼寫」講清楚,而這正是短代碼 API 存在的理由——WordPress 基於安全考量不允許文章內容執行 PHP,短代碼就是在「不能寫 PHP」跟「想要動態內容」之間搭的橋。
讀完這篇,你會從兩行程式碼的最小範例開始,一路做到能帶屬性、能包內容、輸出前也處理好安全性的短代碼,還會知道自己寫的程式碼該放在哪裡,才不會被主題更新洗掉。先從短代碼實際運作的機制講起,再一步步往下拆成能自己動手寫的程式碼。
WordPress 短代碼是什麼?方括號如何變成畫面內容
短代碼的外觀很單純,一對方括號中間放一個名稱,後面可以再接零到多個「屬性等於值」的配對,例如 。這串文字被貼進文章內容儲存起來時,看起來就只是一段普通文字;真正發生轉換的時間點,是 WordPress 準備把文章顯示到瀏覽器的那一刻。內容會先經過 the_content 這個過濾器,過濾器鏈裡有一關專門負責掃描全文,找出符合方括號語法、而且已經註冊過的標記,換成該標記對應函式回傳的內容再顯示出來。沒被註冊過的方括號,就會原封不動當成一段文字顯示,這點在後面排查問題時很重要。

短代碼 API 從 WordPress 2.5 版就存在,而且它不是外掛才有的機制,WordPress 核心本身就內建了六個短代碼:caption、gallery、audio、video、playlist、embed,分別對應核心的 wp_audio_shortcode()、wp_video_shortcode() 等函式。也就是說,你貼進文章的 ,跟外掛作者幫你寫的短代碼走的是同一套機制,只是核心那幾個已經幫你寫好函式並註冊完成。
之所以要繞這一圈用方括號,而不是直接讓使用者在文章內容裡貼 PHP 程式碼,理由是安全。文章內容存在資料庫裡,任何能編輯文章的人都能寫進去,如果 WordPress 讓內容區塊直接執行 PHP,等於任何一個投稿者帳號都能在伺服器上跑任意程式碼,風險太高。短代碼 API 就是在「不能寫 PHP」跟「想要顯示動態內容」這兩個相衝的需求之間搭出來的橋,你事先在佈景主題或外掛的程式碼裡把函式寫好、註冊好,文章內容裡只留一段安全的方括號標記去呼叫它。短代碼從頭到尾都是同一件事的變化題:寫一個函式,再把它註冊成短代碼,並不是在學一種新語言,只是在學怎麼跟 the_content 這個過濾器打交道。
只靠兩行程式碼註冊的第一個短代碼
最小可行的短代碼只需要兩個動作:寫一個回傳固定文字的函式,再呼叫 add_shortcode() 把它跟一個標記名稱綁在一起。標準寫法是這樣:
function wporg_shortcode_func( $atts = array(), $content = null ) {
return '這段文字是短代碼輸出的內容';
}
add_shortcode( 'wporg', 'wporg_shortcode_func' );Code language: PHP (php)
add_shortcode() 只收兩個參數:第一個是標記名稱,也就是文章裡方括號要打的那個字(例如上面的 wporg);第二個是要被呼叫的函式名稱。這行程式碼要寫在佈景主題或外掛的 PHP 檔案裡,不是寫在文章內容裡,文章編輯區只負責打 [wporg] 這串方括號,實際的函式定義跟註冊都得放在程式碼檔案,這正好銜接上一節講的安全前提,文章內容不能執行 PHP,程式邏輯只能寫在檔案裡。
回呼函式有一個容易踩的細節,一定要用 return 把內容傳回去,不能用 echo 直接印出來。原因是短代碼解析器拿到的是函式的回傳值,再把這個回傳值插入到內容裡該出現的位置;如果函式裡用 echo,這段文字會在函式被呼叫的當下就直接印出來,跳過了插入內容的正常流程,遇到某些呼叫情境,例如另一個函式先接住這個短代碼的輸出、要拿去做進一步處理,就會輸出在錯誤的位置,或是直接失效。
標記名稱本身也有命名規則,全部使用小寫字母,數字跟底線可以放心用,但要盡量避開連字號(-)。這不是美觀上的建議,而是解析器實際運作上的地雷。名稱定好、函式寫完、add_shortcode() 註冊完成,接下來只要在文章裡打上 [wporg],the_content 顯示的時候就會把這串方括號換成函式回傳的文字。
shortcode_atts() 讓短代碼帶入不同屬性值
到目前為止的短代碼只能輸出固定的內容,實用價值有限;真正讓短代碼派上用場的,是同一個標記能靠帶入不同屬性,長出不同的結果。屬性語法長這樣:[標記 foo="bar" bar="bing"],短代碼系統會把這些屬性值整理成一個關聯陣列,傳給回呼函式的第一個參數 $atts,上面這個例子會得到 array( 'foo' => 'bar', 'bar' => 'bing' )。這裡有一個很多人會踩的型別誤區,如果文章裡完全沒帶任何屬性,只打 [標記],$atts 拿到的不是空陣列,而是一個空字串。如果函式裡沒有先做防呆就直接把 $atts 當陣列操作,遇到沒帶屬性的呼叫方式就會出錯。
shortcode_atts() 就是用來處理這個問題、順便補上預設值的標準做法。它收三個參數:第一個是預設值陣列,列出這個短代碼認得哪些屬性、每個屬性沒被指定時要用什麼值;第二個是剛剛拿到的 $atts;第三個是短代碼名稱(可省略,但填了會多觸發一個過濾鉤子 shortcode_atts_{$shortcode})。它的回傳值只會保留第一個參數裡列出的鍵,使用者在文章裡多打的、或是打錯字的屬性,會被直接過濾掉,不會出現在回傳結果裡;反過來,使用者沒指定的屬性,就用預設值遞補。

還有一個容易被忽略的細節,使用者在文章裡打屬性名稱時,大小寫不見得會照規矩來,例如寫成 Size="Medium" 而不是 size="medium"。標準做法是在丟進 shortcode_atts() 之前,先用 array_change_key_case( (array) $atts, CASE_LOWER ) 把屬性的鍵名統一轉成小寫,再進行合併,這樣使用者不管打大寫小寫,函式都能正確讀到值。把這三步串起來,一個能吃屬性的短代碼大致長這樣:
function wporg_bartag_func( $atts ) {
$atts = array_change_key_case( (array) $atts, CASE_LOWER );
$a = shortcode_atts(
array(
'foo' => 'something',
'bar' => 'something else',
),
$atts
);
return "foo = {$a['foo']}, bar = {$a['bar']}";
}
add_shortcode( 'bartag', 'wporg_bartag_func' );Code language: PHP (php)
這支函式不管使用者在文章裡打 [bartag foo="apple"](只帶一個屬性、另一個吃預設值),還是完全不帶屬性只打 [bartag],都能穩定回傳結果,不會因為型別或大小寫的落差而出錯。
把內容包在起始與結束標籤之間的短代碼寫法
前面幾個範例的短代碼都是自我封閉(self-closing),文章裡只打一個 [標記],沒有對應的結尾標籤。短代碼還有另一種封閉式(enclosing)的型態,寫法是 [標記]被包住的內容[/標記],中間包住的文字會被當成一段獨立的內容傳給回呼函式,函式可以對這段內容加工後再輸出。
分辨這兩種型態,關鍵在回呼函式的第二個參數 $content。自我封閉的呼叫方式下,$content 的值是 null;封閉式的呼叫方式下,$content 會拿到頭尾標籤之間的原始文字。函式定義時把 $content 的預設值設成 null,就是為了讓函式內部可以用 is_null( $content ) 判斷目前這次呼叫是哪一種型態,走不同的處理邏輯。
實務上封閉式短代碼最常見的用途,是把一段內容包上容器標籤或套用樣式。舉一個帶標題的區塊為例,短代碼接收一個 title 屬性,加上頭尾標籤之間的內容,組合成一個帶標題的 <div>:
function wporg_box_func( $atts, $content = null ) {
$atts = array_change_key_case( (array) $atts, CASE_LOWER );
$a = shortcode_atts( array( 'title' => '' ), $atts );
$output = '<div class="wporg-box">';
if ( ! empty( $a['title'] ) ) {
$output .= '<h4>' . esc_html( $a['title'] ) . '</h4>';
}
$output .= apply_filters( 'the_content', $content );
$output .= '</div>';
return $output;
}
add_shortcode( 'wporgbox', 'wporg_box_func' );Code language: PHP (php)
文章裡打 [wporgbox title="注意事項"]這裡放要提醒讀者的內容[/wporgbox],就會輸出一個帶標題的提示框。這裡標題屬性用 esc_html() 包住再輸出,內容則透過 apply_filters( 'the_content', $content ) 處理,這一步等同讓包在裡面的文字也套用一次內容過濾器該有的處理(例如自動加上段落標籤),但這裡示範的輸出方式還沒處理完整的安全性顧慮。
短代碼解析只跑一次,巢狀用法要呼叫 do_shortcode()
封閉式短代碼有一個新手很容易踩、卻很少被講清楚的技術陷阱。短代碼解析器對文章內容只會掃過一次(single pass),這代表如果某個封閉式短代碼包住的 $content 裡面,又寫了另一個短代碼,預設情況下這個內層的短代碼不會被解析,只會原封不動當成一般文字顯示,讀者看到的會是還沒被轉換的方括號文字,而不是預期中的巢狀結果。
解法很直接,在回呼函式裡對 $content 手動呼叫一次 do_shortcode(),讓解析器針對這段內容再跑一次解析:
function wporg_shortcode_func( $atts = array(), $content = null ) {
$content = do_shortcode( $content );
return $content;
}
add_shortcode( 'wporg', 'wporg_shortcode_func' );Code language: PHP (php)
這一行補上去之後,就算 $content 裡面藏著另一個已註冊的短代碼標記,也會被遞迴解析出來,正確顯示成該標記的輸出結果,而不是留著一串沒被處理的方括號文字。

還有一個相關的雷點,跟「只掃一次」是同一個技術根源。同一個標記混用自我封閉與封閉式兩種寫法時,解析器不會把它們當成兩個獨立的短代碼分別處理。舉例來說,[wporg] 一段文字 [wporg]另一段內容[/wporg] 這種寫法,解析器並不會理解成「一個自我封閉的 [wporg] 加上一個獨立的封閉式 [wporg]...[/wporg]」,而是會把從第一個 [wporg] 到第一個 [/wporg] 之間的所有文字,整段當成一個封閉式短代碼的內容去處理,也就是「一段文字 [wporg]另一段內容」這一大串全部變成 $content。要避免這個問題,同一個標記在同一篇文章裡,最好統一只用自我封閉或只用封閉式其中一種寫法,不要混著打。
短代碼函式該寫進子佈景主題還是專屬外掛
短代碼的函式跟 add_shortcode() 這行程式碼,實際上要寫在哪個檔案,會決定它日後穩不穩定。最直接、卻最不建議的做法,是直接寫進父佈景主題的 functions.php。這個檔案只要主題一有更新,就會被官方版本整批覆蓋,寫進去的短代碼會連同其他自訂修改一起消失。
比較穩妥一點的做法,是寫進子佈景主題的 functions.php。WordPress 載入 functions.php 的順序,是先載入子佈景主題那份,再載入父佈景主題那份,所以子主題的 functions.php 不會被父主題更新覆蓋。不過這個做法還是有限制,一旦哪天你換掉整個佈景主題(包含子主題),寫在裡面的短代碼函式也會跟著佈景主題一起消失,前台會直接顯示還沒被解析的方括號原始文字,讀者一眼就看得出來哪裡出了問題。
最穩定的做法,是把短代碼寫成一個獨立的「站點專屬外掛」(site-specific plugin),完全不依附任何佈景主題。判斷該不該用外掛而不是主題來放程式碼,官方主題手冊有一句話講得很直白,能不能在佈景主題裡做出跟外掛一樣的功能,不代表就該這麼做,如果要做的功能不應該因為換了網站設計就跟著消失,那就該放進外掛,佈景主題本身只該負責網站的外觀設計。短代碼屬於內容功能而不是外觀,換句話說,即使日後整個網站重新設計、換掉佈景主題,自己寫的短代碼也應該繼續正常運作,這正是站點專屬外掛真正的價值。
實務上還有一個小技巧,把短代碼相關的函式集中放在一個獨立的檔案(例如 shortcodes.php),再用 include 引進主要的程式碼檔案裡,而不是把所有函式一股腦全塞進同一份 functions.php。這樣做的好處是日後要維護、除錯或是回頭檢查某個短代碼的邏輯時,可以直接開對應的檔案,不必在一份越長越肥大的 functions.php 裡面翻找。
esc_attr() 與 wp_kses() 跳脫短代碼裡使用者能控制的內容
自己寫短代碼跟安裝別人寫好的外掛短代碼,最大的差別之一在安全性。外掛作者已經幫忙處理過輸出前該做的跳脫,自己動手寫,這一步就得自己做好,不能因為是自己的網站就跳過。
跳脫的核心原則是依輸出情境選對應的函式,而且要在輸出的當下做,不是在資料存進去的時候處理一次就算了。屬性值輸出用 esc_attr(),要印進 HTML 的一般文字用 esc_html(),網址則用 esc_url()。前面帶標題的區塊那個範例裡,標題屬性就是用 esc_html( $a['title'] ) 包住才輸出,理由很單純,title 屬性的值來自使用者在文章裡打的字,如果沒有跳脫就直接印進 HTML,任何能編輯文章的帳號就有機可乘,塞進帶有 <script> 標籤的內容,讓瀏覽器執行不該執行的程式碼。
如果短代碼需要放行使用者帶入的一小段 HTML,例如允許使用者在屬性或內容裡使用 <b>、<a> 這類簡單標籤,不應該整段原樣輸出,而要改用 wp_kses() 或它的簡化版本 wp_kses_post(),限制允許出現的標籤與屬性白名單。wp_kses_post() 套用的是文章內容本身允許的標籤規則,等於用跟正文一致的標準去篩選這段使用者帶入的內容,比自己手動列一套規則更省事,也更不容易漏掉邊角案例。封閉式短代碼裡的 $content,如果不是走 apply_filters( 'the_content', $content ) 這條路,套用 wp_kses_post() 也是常見的處理方式。原則只有一個,先想清楚這段值最後會被印到哪種情境裡(屬性、純文字、還是允許部分標籤的 HTML),再挑對應的函式,不要偷懶全部原樣輸出。
短代碼在文章、小工具與範本檔案裡的用法差異
短代碼不是只能貼在文章正文裡,不同的使用場景要用不同的方式呼叫,才會實際生效。
在區塊編輯器的文章或頁面內容裡,短代碼有專用的「短代碼」區塊,直接插入這個區塊、貼上方括號語法就會生效,這是短代碼最原生的使用場景,前面所有範例打的 [標記] 都是指這裡。
在小工具裡的狀況稍微複雜一點。現代 WordPress 的文字小工具內容,在輸出前會套用 widget_text_content 這個核心過濾器,這個過濾器對應的處理鏈跟 the_content 部分重疊(包含自動加段落標籤等處理),也包含解析短代碼,所以現代版本的文字小工具貼上方括號語法,預設就會被正確處理成對應的輸出。如果用的是比較舊版本的佈景主題,或是自訂的小工具區域沒有接上這個過濾器鏈,短代碼就不會被解析,這種情況下要自己額外掛一行 add_filter( 'widget_text', 'do_shortcode' );,補上解析短代碼的能力。
在佈景主題的 PHP 範本檔案裡,方括號語法完全不會生效,短代碼語法本身是設計給文章內容用的,範本檔案不會經過 the_content 那套過濾器鏈。如果想在範本檔案裡顯示跟短代碼一樣的效果,寫法要改成直接呼叫函式:echo do_shortcode( '[標記]' );。這行程式碼會找出內容字串裡已經註冊的短代碼標記,替換成對應函式的回傳值再印出來,效果等同在文章裡打了這個方括號。這裡有個行為要先知道,如果 [標記] 根本沒被註冊,例如相關檔案沒被 include 進來,do_shortcode() 不會報錯,只會把內容原樣(含方括號文字)傳回。
短代碼沒有輸出預期結果,最常掉進的三個陷阱
畫面沒顯示出預期效果,原因通常脫離不了前面幾節提過的某個機制,最常見的是以下三種狀況。
函式標記用了連字號,短代碼註冊不穩定
短代碼標記命名有明確規則:一律使用小寫字母,數字跟底線可以放心用,但要盡量避開連字號(-)。這條規則常常被忽略,因為讀者已經習慣了另一套命名慣例,網頁上常見的技術文件、CSS class、URL 路徑,普遍用連字號分隔單字(像 my-custom-box),照著這個慣例去取短代碼標記名稱,看起來很自然,卻剛好踩到短代碼解析上不穩定的地雷。
正確的命名對照很直接,把 my-custom-box 改成 my_custom_box 或直接寫成 mycustombox,用底線取代連字號即可。這不是美觀上的偏好問題,而是短代碼解析器實際運作時的建議,命名一開始就照規矩來,比日後排查「為什麼這個短代碼有時候解析得出來、有時候解析不出來」要省事得多。
自我封閉與封閉式兩種寫法混用,解析器認錯範圍
前面「巢狀短代碼」那一節已經講過技術原因,這裡換一個角度,從除錯的角度再講一次。如果發現自己寫的短代碼後面那段內容不見了,或是多包了一大段原本不該被包進去的文字,第一件要檢查的事,就是有沒有把同一個標記的自我封閉寫法跟封閉式寫法,混用在同一篇文章裡。
短代碼解析器沒辦法正確處理這種混用,遇到同一個標記同時出現自我封閉跟封閉式兩種形式時,它會把從第一個標記到第一個結尾標籤之間的所有內容,整段當成一個封閉式短代碼的內容去處理,中間原本該是獨立內容的那一段文字,會被一起吞進 $content 裡,變成解析結果跟預期完全對不上。排查的做法很單純,整篇文章搜一次同一個標記名稱,確認它要嘛全部用自我封閉的 [標記],要嘛全部用封閉式的 [標記]內容[/標記],不要兩種混著打。
忘記註冊或沒有把檔案引入,短代碼直接顯示成方括號文字
前台如果原封不動印出 [標記] 這串文字,而不是預期中的輸出結果,多數人第一直覺會去檢查函式裡的邏輯寫錯了什麼,但最常見的原因其實不是邏輯問題,而是 add_shortcode() 那一行根本沒被執行到。常見的情境是把短代碼函式寫進獨立的檔案,前面「該放在哪裡」那節提過的做法,卻忘了在 functions.php 裡用 include 把這個檔案引進來,或是引入的路徑寫錯了,導致這個檔案裡的程式碼從頭到尾都沒有被載入過。
這個狀況特別難排查的原因是,do_shortcode() 遇到沒被註冊的標記,並不會報任何錯誤或警告,只會把內容原樣(連方括號一起)傳回去顯示,讀者只會看到一串方括號文字,畫面上不會出現任何線索告訴哪裡出了問題。排查的方法是先確認檔案有沒有真的被 include 進來,再確認 add_shortcode() 那一行有沒有實際被執行到,可以暫時在函式最前面加一行測試用的輸出(例如寫進錯誤紀錄),確認函式本身真的有被呼叫,藉此判斷問題出在函式邏輯還是根本沒被載入這兩種完全不同的方向。
短代碼從頭到尾都只是同一件事的變化題:寫一支函式,讓它有能力處理屬性、處理內容,再用 add_shortcode() 把它跟一個安全的方括號標記綁在一起。真正決定它好不好用、穩不穩定的,往往不是那支函式邏輯寫得多複雜,而是命名有沒有避開連字號、輸出前有沒有做好跳脫、程式碼有沒有放進不會被主題更新洗掉的地方。下次在後台看到別人寫的外掛用一串方括號就做出一個複雜的畫面區塊,就會知道它背後其實就是這幾件事疊起來的結果,而現在,也有能力自己動手做一個。



