本文约 5,310 字,预计阅读 14 分钟。文中命令与配置均经过实测,可直接参考执行。
引言:让编辑自己插入复杂模块
运营要在文章中间插一个”三栏特性对比表”,或者一个带数据的统计卡片。让他写 HTML 不现实,复制粘贴几次之后样式就乱了。
短代码(Shortcode)就是为这个场景设计的:开发者把 HTML+逻辑封装成 [feature title="xxx"]内容[/feature],编辑只要记住这一个标签。
它也是插件生态的基础——WooCommerce、Contact Form 7、绝大部分页面增强插件,核心交互方式都是短代码。
原理分析:短代码是怎么被解析的
WordPress 在输出正文时执行 do_shortcode()(挂在 the_content 过滤器上),流程:
- 用正则扫描正文,找出所有已注册的短代码标签;
- 按注册顺序逐个调用对应的回调函数;
- 把回调的返回值替换掉原标签位置。
关键点有三个:
关键点一:回调必须 return,不能 echo。
// 错误:直接 echo 会导致内容跑到文章开头
add_shortcode( 'hello', function() {
echo '<p>Hello</p>';
} );
// 正确
add_shortcode( 'hello', function() {
return '<p>Hello</p>';
} );
这是新手最常犯的错。因为 do_shortcode 是在过滤器里执行的,echo 会在内容还没输出时就打印出来。
关键点二:短代码只在执行了 do_shortcode 的地方生效。
默认 the_content(正文)会执行,但以下地方默认不执行:
- 摘要
the_excerpt - 小工具文本
widget_text - 自定义字段的输出
- 主题的某些自定义区域
要开启:
add_filter( 'the_excerpt', 'do_shortcode' );
add_filter( 'widget_text', 'do_shortcode' );
add_filter( 'term_description', 'do_shortcode' );
关键点三:属性名会被转成小写。
[box Title="x"] 在回调里收到的是 $atts['title'],不是 Title。
保姆级实操
一、最简形式
add_shortcode( 'mmf_year', function() {
return date( 'Y' );
} );
用法:[mmf_year],输出 2026。
二、带参数(自闭合短代码)
add_shortcode( 'mmf_box', function( $atts ) {
$atts = shortcode_atts( array(
'title' => '提示',
'type' => 'info', // info / warning / danger
'icon' => '',
), $atts, 'mmf_box' );
$type = sanitize_html_class( $atts['type'] );
return sprintf(
'<div class="mmf-box mmf-box-%1$s">%2$s<div class="mmf-box-body"><strong>%3$s</strong></div></div>',
esc_attr( $type ),
$atts['icon'] ? '<span class="mmf-box-icon">' . esc_html( $atts['icon'] ) . '</span>' : '',
esc_html( $atts['title'] )
);
} );
用法:[mmf_box type="warning" icon="⚠" title="注意备份"]
shortcode_atts() 的三个参数:默认值数组、用户传入的数组、短代码名(第三个参数用于 shortcode_atts_{$shortcode} 过滤器,让别人能改默认值)。
三、带内容的短代码(闭合式)
add_shortcode( 'mmf_panel', function( $atts, $content = null ) {
$atts = shortcode_atts( array(
'title' => '',
), $atts, 'mmf_panel' );
if ( null === $content ) {
return '';
}
// 关键:对内容再执行一次 do_shortcode,支持嵌套
$content = do_shortcode( shortcode_unautop( $content ) );
return sprintf(
'<div class="mmf-panel"><h4>%s</h4><div class="mmf-panel-body">%s</div></div>',
esc_html( $atts['title'] ),
$content
);
} );
用法:
[mmf_panel title="操作步骤"]
1. 备份数据库
2. 修改配置
[mmf_box type="danger" title="别在生产环境直接改"]
[/mmf_panel]
嵌套的两个必要处理:
do_shortcode( $content ):让内容里的内层短代码也被解析;shortcode_unautop():去掉 WordPress 自动加的<p>和<br>,否则短代码里会出现莫名其妙的空段落。
四、输出缓冲(复杂 HTML 时更好读)
add_shortcode( 'mmf_recent', function( $atts ) {
$atts = shortcode_atts( array(
'num' => 5,
'cat' => '',
), $atts, 'mmf_recent' );
$args = array(
'posts_per_page' => absint( $atts['num'] ),
'post_status' => 'publish',
);
if ( $atts['cat'] ) {
$args['cat'] = absint( $atts['cat'] );
}
$q = new WP_Query( $args );
if ( ! $q->have_posts() ) {
return '';
}
ob_start();
echo '<ul class="mmf-recent">';
while ( $q->have_posts() ) {
$q->the_post();
printf(
'<li><a href="%s">%s</a><span class="date">%s</span></li>',
esc_url( get_permalink() ),
esc_html( get_the_title() ),
esc_html( get_the_date( 'Y-m-d' ) )
);
}
echo '</ul>';
wp_reset_postdata();
return ob_get_clean();
} );
ob_start() + ob_get_clean() 是写长 HTML 的标准做法,比字符串拼接可读性好得多。别忘了 wp_reset_postdata()。
五、转义规则(安全)
短代码内容里如果用户要写 HTML,需要正确处理:
$content = wp_kses_post( $content ); // 允许文章级别的 HTML(a, strong, em 等)
$content = do_shortcode( $content ); // 再解析嵌套短代码
千万别 strip_tags()(会清掉合法格式),也别完全不处理(XSS 风险)。
属性值一定要 esc_attr()、esc_html()、esc_url()。
六、在主题/模板里直接调用
echo do_shortcode( '[mmf_recent num="8"]' );
七、检查短代码是否已注册
if ( shortcode_exists( 'mmf_box' ) ) { ... }
wp eval 'global $shortcode_tags; print_r(array_keys($shortcode_tags));'
常见踩坑与失败案例
- 案例一:短代码输出跑到文章最前面。原因:回调里用了
echo而不是return。 - 案例二:短代码在小工具里显示成
[mmf_box]原文。原因:widget_text过滤器没挂do_shortcode。加:
php
add_filter( 'widget_text', 'do_shortcode' );
- 嵌套短代码不被解析:外层回调忘了
do_shortcode( $content )。注意:同名短代码不能嵌套自己([mmf_panel][mmf_panel][/mmf_panel][/mmf_panel]会解析失败,这是 WordPress 正则的限制)。 - 自动
<p>破坏布局:WordPress 的wpautop会在短代码前后插入<p>。用shortcode_unautop()或者在the_content之前注册短代码。也可以把wpautop延后:
php
remove_filter( 'the_content', 'wpautop' );
add_filter( 'the_content', 'wpautop', 12 ); // 放到 do_shortcode 之后
- 属性名大小写:
[box Title="x"]收到的是$atts['title']。 - 案例三:短代码里做了数据库查询但没
wp_reset_postdata(),导致页面下方的”相关文章”全部显示成同一篇。这是最隐蔽的一类 bug。 - 参数里有空格:
[mmf_box title="hello world"]必须用引号包裹属性值,否则只收到hello。 - 缓存问题:短代码输出动态内容(如当前时间、随机文章)时,页面缓存会让它”不更新”。要用 AJAX 或排除缓存。
总结
短代码开发记住五条:回调必须 return 不能 echo;用 shortcode_atts() 处理默认值;带内容的闭合式要 do_shortcode($content) + shortcode_unautop() 才支持嵌套且不会出现多余 <p>;复杂 HTML 用 ob_start() + ob_get_clean(),循环后一定 wp_reset_postdata();所有输出 esc_*() 转义。默认只在正文生效,要在摘要、小工具、自定义字段里用就得手动挂 do_shortcode 过滤器。另外同名短代码不能自嵌套,这是正则解析的硬限制。
