本文约 4,948 字,预计阅读 13 分钟。文中命令与配置均经过实测,可直接参考执行。
引言:第一个插件应该小到什么程度
学 WordPress 插件开发最常见的障碍不是概念难,而是起步太重。很多人一开始就去看大型插件的源码,看到几十个目录、一堆类和接口,直接劝退。
实际上一个能被 WordPress 识别、能在前台输出内容的插件,只需要一个 PHP 文件、十几行代码。剩下的所有复杂度——设置页面、数据库表、REST 接口、后台列表——都是在这十几行的基础上长出来的。先把最小闭环跑通,你会对「插件到底是什么」有一个具体而非抽象的理解。
本文做的就是这个最小闭环:创建一个目录、写一个主文件、加一个短代码、在文章里输出一段文本、从后台启用、验证生效。全程不需要任何构建工具,一个文本编辑器和一个能跑 WordPress 的环境就够。
原理分析:一个插件跑起来要经过哪几步
理解流程比记住代码更重要。从文件出现到内容输出,一共五步:
第一步:WordPress 扫描目录。 后台进入插件页(或执行 wp plugin list)时,WordPress 会用 get_plugins() 扫描 wp-content/plugins/ 下的 PHP 文件,读取头部注释,生成插件清单。此时插件处于「已发现但未激活」状态。
第二步:激活。 你点「启用」,WordPress 把插件的主文件路径写进 wp_options 表的 active_plugins 数组。这是一个序列化的数组,顺序即加载顺序。
第三步:加载。 之后的每个请求,WordPress 读取 active_plugins,依次 include 每个主文件。注意是每个请求都 include 一次,前台、后台、AJAX、REST 请求全部如此(可通过条件判断跳过)。
第四步:注册。 主文件被执行时,你的代码调用 add_action() / add_shortcode() 等函数,把回调注册进全局的钩子数组 $wp_filter。这一步只是「登记」,还没有真正执行。
第五步:触发。 WordPress 执行到对应的时机(比如渲染文章内容时执行 the_content 过滤器),从 $wp_filter 里找出已注册的回调并执行。你的代码此时才真正运行,输出内容。
理解了这个流程,就能明白插件开发里最重要的两个习惯:主文件只做注册,不做重活;任何耗时逻辑都要判断当前请求是不是真的需要它。
另外要注意短代码的工作方式:短代码 [hello_world] 写在文章正文里,WordPress 在输出 the_content 时会用正则扫描方括号,把匹配到的名字交给 add_shortcode() 注册的回调处理,回调的返回值替换掉原文里的短代码。所以短代码回调必须返回值,不能直接 echo。这是新手最常踩的坑——直接 echo 的话,内容会出现在页面最顶部而不是短代码所在位置。
保姆级实操
第一步:创建插件目录与主文件
cd wp-content/plugins
mkdir mmf-hello
cd mmf-hello
目录名用小写字母加连字符,加上你自己的前缀避免与他人冲突。主文件名与目录名保持一致。
第二步:写主文件
<?php
/**
* Plugin Name: MMF Hello World
* Description: 第一个 WordPress 插件:注册一个短代码并输出问候语。
* Version: 1.0.0
* Requires at least: 6.0
* Requires PHP: 7.4
* Author: mimangfei
* License: GPL-2.0-or-later
* License URI: https://www.gnu.org/licenses/gpl-2.0.html
* Text Domain: mmf-hello
*/
defined( 'ABSPATH' ) || exit;
/**
* 短代码回调:返回内容,不要 echo。
*
* 用法:[mmf_hello name="张三"]
*/
function mmf_hello_shortcode( $atts ) {
$atts = shortcode_atts(
array(
'name' => '世界',
'size' => '16',
),
$atts,
'mmf_hello'
);
$name = sanitize_text_field( $atts['name'] );
$size = absint( $atts['size'] );
if ( $size < 8 ) {
$size = 8;
}
if ( $size > 72 ) {
$size = 72;
}
return sprintf(
'<p class="mmf-hello" style="font-size:%dpx">%s</p>',
$size,
sprintf(
/* translators: %s 是访客的名字 */
esc_html__( '你好,%s!这是你的第一个插件输出的内容。', 'mmf-hello' ),
esc_html( $name )
)
);
}
add_shortcode( 'mmf_hello', 'mmf_hello_shortcode' );
这段代码里有三个值得注意的点:
shortcode_atts()把用户传入的属性与默认值合并,同时过滤掉未声明的属性;sanitize_text_field()与absint()做输入净化,这是插件安全的底线(相关函数的完整对照见 数据验证与转义函数);esc_html()与esc_html__()做输出转义,避免用户传入的名字里带 HTML 造成 XSS。
第三步:验证语法
php -l wp-content/plugins/mmf-hello/mmf-hello.php
输出 No syntax errors detected 才继续。这一步能拦住绝大多数低级错误。
第四步:从后台或命令行启用
# 确认 WordPress 已识别到插件
wp plugin list --status=inactive
# 启用
wp plugin activate mmf-hello
# 确认状态
wp plugin list --status=active --fields=name,version,status
也可以在后台「插件 → 已安装插件」里找到「MMF Hello World」点启用。
第五步:在文章里使用并验证
新建一篇文章,正文写入:
[mmf_hello name="李四" size="20"]
保存后访问前台,应该看到一句带样式的问候语。如果看到的是方括号原文,说明插件没启用或短代码名写错。
第六步:加一个启用时的初始化动作
真实插件通常需要在启用时写默认配置。用 register_activation_hook():
register_activation_hook( __FILE__, 'mmf_hello_activate' );
function mmf_hello_activate() {
if ( ! get_option( 'mmf_hello_options' ) ) {
add_option(
'mmf_hello_options',
array(
'default_name' => '世界',
'default_size' => 16,
)
);
}
// 刷新重写规则,若插件注册了自定义文章类型则必须做
flush_rewrite_rules();
}
register_activation_hook() 的第一个参数必须是插件主文件的路径,因此传 __FILE__。它在启用时只执行一次。
第七步:加一个后台提示,确认插件在工作
调试阶段最常用的手段:
add_action( 'admin_notices', function () {
if ( ! current_user_can( 'manage_options' ) ) {
return;
}
printf(
'<div class="notice notice-success"><p>%s</p></div>',
esc_html__( 'MMF Hello World 插件已启用。', 'mmf-hello' )
);
} );
第八步:停用与删除
wp plugin deactivate mmf-hello
wp plugin delete mmf-hello
删除会触发 uninstall.php(如果存在)。本例还没有,实际项目里应该补上,规范写法见 uninstall.php 清理数据。
常见踩坑
-
短代码回调里用
echo而不是return:内容会跑到页面顶部。短代码必须返回字符串。 -
函数命名没有前缀:
function hello()这种名字极容易与其他插件冲突,触发Cannot redeclare function。所有全局函数、类、常量都要加你的前缀。 -
忘记
defined( 'ABSPATH' ) || exit;:意味着这个文件可以被直接通过 URL 访问,是个信息泄露点。 -
不转义输出:用户传
name="<script>alert(1)</script>"就会被原样输出。 -
在启用钩子里直接 echo:激活钩子执行时会发生重定向,echo 的内容会导致
headers already sent错误。 -
案例: 一个新手把插件主文件保存成了 UTF-8 with BOM,PHP 能跑,但 WordPress 读取头部时把 BOM 当成了文件开头,导致
Plugin Name解析不到,后台插件列表里永远看不到它。排查了一整天才发现是编辑器的编码设置问题。教训是:插件不被识别时,第一件事是检查文件编码与头部注释格式,而不是怀疑代码逻辑。 -
把整个插件写在一个文件里越写越长:超过 300 行就该拆到
includes/目录。
总结
一个插件的最小闭环是:目录 → 主文件头部 → 注册钩子或短代码 → 激活 → 验证输出。五步走通,剩下的都是在这之上加东西。
必须养成四个习惯:主文件开头加 ABSPATH 检查;所有全局标识符加前缀;输入用 sanitize_*、输出用 esc_*;短代码回调 return 不 echo。
下一步的学习路径:想让插件能被配置,看 创建自定义设置页面;想真正理解钩子机制,看 add_action 与 add_filter 区别——那是插件开发的地基。
