信科网络 · 11 年只做 WordPress | 建站 / 主题 / 插件 / 商城 / 迁移 / 托管 | 累计服务 3000+ 企业 | 源码全交付
服务热线:13280692153 邮箱:web@wordpressx.com
工作日 08:30-18:0013280692153 免费诊断
首页/WordPress 教程/WordPress 钩子入门:action、filter 与插件开发
BLOG

WordPress 钩子入门:action、filter 与插件开发

分类 发布 2026-10-02

action 和 filter 的区别,说到底只有一句话:action 是「走到这里时顺便做一件事」,filter 是「把这个值改一下再交回去」。前者不需要返回值,后者必须返回。新手把 filter 回调写成不 return,页面不会报错,只会安静地丢内容——标题变空白、正文消失,这类问题能查一下午。

一、本质区别对照

维度 action filter
用途 在某个时机插入执行代码 修改传入的数据再返回
是否需要返回值 不需要,返回值会被忽略 必须 return,否则数据被置空
注册函数 add_action() add_filter()
触发函数 do_action() apply_filters()
典型场景 输出统计代码、保存文章后同步数据、发送邮件 改标题、改摘要、改价格、改查询参数

底层实现其实是同一套:两者都存在 $wp_filter 全局数组里,add_action() 内部就是调用了 add_filter()。区别只是 do_action() 丢弃返回值,apply_filters() 把第一个回调的返回值依次传给下一个。所以严格说 action 也能返回值,只是没人接。

写自定义钩子时同理:想让别人改数据就用 apply_filters(),第一个参数是钩子名,第二个是默认值,后面才是附加参数;想让别人在你的流程里插一脚就用 do_action()。命名一律加前缀,避免和其他插件撞名。这两行代码是插件之间互相扩展的唯一正规通道,主题里硬改别人的插件文件是最不该做的事。

二、add_action 的四个参数

签名是 add_action( $hook, $callback, $priority = 10, $accepted_args = 1 )。后两个参数是绝大多数钩子失效的原因。

  • priority:数字越小越早执行。默认 10。想让自己的代码在主题之后跑,写 20;想在所有人之前拦截,写 1 甚至负数。
  • accepted_args:回调能接收几个参数。钩子传了 3 个参数,但你写默认的 1,回调里就只能拿到第一个,后两个是 null。要接全部参数必须显式写 3。
add_action( 'wp_footer', 'xk_footer_note', 20 );
function xk_footer_note() {
    echo '<!-- built by www.wordpressx.com -->';
}

// 移除别人挂的钩子,优先级必须与添加时完全一致
remove_action( 'wp_footer', 'some_plugin_footer', 10 );

// save_post 传 3 个参数,必须声明 accepted_args 为 3
add_action( 'save_post', 'xk_on_save', 10, 3 );
function xk_on_save( $post_id, $post, $update ) {
    // $post 是 WP_Post 对象,$update 是布尔值
}

remove_action() 的三个参数必须与 add_action() 完全对得上,优先级差一个数字就移除不掉。这也是「插件功能关不掉」的常见原因:插件用 99 挂的钩子,你按 10 去移除,当然没反应。

回调的命名要带自己的前缀。xk_、公司缩写_ 都行,但一定要加,因为 PHP 函数是全局的,两个插件都定义 footer_note() 会直接 fatal error。用匿名函数可以天然避开重名,代价是没法 remove_action(),调试时也看不出是谁挂的。

三、五个必会的钩子

钩子 类型 触发时机 常见用途
wp_head action </head> 之前 注入 meta、统计代码、预加载
wp_footer action </body> 之前 脚本、客服悬浮窗、延迟加载
init action 每次请求初始化时 注册自定义文章类型、重写规则
the_content filter 输出正文时 追加声明、插入广告、替换关键词
save_post action 文章保存之后 同步外部数据、生成缓存、写自定义字段

两个使用注意:the_content 是过滤器不是动作,很多人调用 add_action( 'the_content', ... ) 也能跑起来,因为底层同一套,但语义上是错的,而且一旦回调没 return,正文就没了。wp_head 里不要 echo 大段 HTML,它会被塞进 head,拖慢首屏渲染,需要结构化的内容就拆到 wp_body_open 或 wp_footer。

再补几个实际开发里绕不开的:after_setup_theme 用来注册缩略图尺寸和菜单位置,比 init 更早;wp_enqueue_scripts 是唯一正确的引入样式脚本的时机,直接写在 wp_head 里会打乱依赖顺序;pre_get_posts 用来改主查询,比如让分类页多显示几篇,注意必须判断 is_main_query() && ! is_admin();wp_ajax_ 前缀加动作名是处理 AJAX 的标准入口,配套还要写 wp_ajax_nopriv_ 给未登录用户。把这些名字记住,比背函数名有用得多。

四、写一个最小插件

新建目录 wp-content/plugins/xk-demo/,里面放一个 xk-demo.php,文件头部的注释块是 WordPress 识别插件的依据,少一行「Plugin Name」后台就看不到它。

<?php
/**
 * Plugin Name: XK Demo Hooks
 * Description: 演示 action 与 filter 的最小插件
 * Version: 1.0.0
 * Author: 信科网络
 */

if ( ! defined( 'ABSPATH' ) ) {
    exit;
}

// filter:正文末尾追加一行声明
add_filter( 'the_content', 'xk_demo_append_note' );
function xk_demo_append_note( $content ) {
    if ( ! is_singular( 'post' ) || ! in_the_loop() || ! is_main_query() ) {
        return $content;
    }
    return $content . '<p>本文由信科网络技术团队整理,转载请注明来源。</p>';
}

// action:保存文章时记录时间
add_action( 'save_post', 'xk_demo_save_time', 10, 3 );
function xk_demo_save_time( $post_id, $post, $update ) {
    if ( wp_is_post_revision( $post_id ) || wp_is_post_autosave( $post_id ) ) {
        return;
    }
    if ( 'post' !== $post->post_type ) {
        return;
    }
    update_post_meta( $post_id, '_xk_saved_at', current_time( 'mysql' ) );
}

这个文件能直接启用。几个必写的防护:文件顶部判断 ABSPATH 防止被直接访问;save_post 里排除修订版本和自动保存,否则每敲一次自动保存就往 meta 表写一行;the_content 里判断主查询,否则摘要、RSS、相关推荐全会被追加一遍声明。

五、钩子不生效时怎么查

钩子机制最大的特点是静默失败。名字拼错、优先级不对、回调没注册上,都不会有任何提示。按下面顺序查基本能定位:

  1. 先确认钩子名存在。在 wp-content/ 下搜 do_action( '你的钩子名' 或 apply_filters(,搜不到就是名字错了或这个版本没有。
  2. 装 Query Monitor,看 Hooks & Actions 面板,它会列出当前页面所有已触发的钩子和挂在上面的事件,一眼能看出你的回调在不在。
  3. 用 has_action() 判断是否注册成功,返回数字是优先级,返回 false 就是没有。
  4. 怀疑优先级时,把 priority 改成 PHP_INT_MAX 试一次,如果突然生效,说明被后面的回调覆盖了。
  5. 确认文件真的被加载。插件没启用、functions.php 里早期 return、或者代码写在了 if ( is_admin() ) 分支外,都会导致不执行。
  6. 最后看 wp-content/debug.log,路径不对、函数不存在这类问题会在里面留下 Fatal error 或 Warning,比在页面上猜快得多。
add_action( 'wp_footer', function () {
    if ( ! current_user_can( 'manage_options' ) ) {
        return;
    }
    echo '<pre>';
    var_dump( has_action( 'the_content', 'xk_demo_append_note' ) );
    var_dump( has_filter( 'the_content' ) );
    echo '</pre>';
}, PHP_INT_MAX );

这段调试代码只在管理员登录时输出,不会影响访客。上线前记得删掉,或者套一层 defined( 'WP_DEBUG' ) && WP_DEBUG。

七、写在 functions.php 还是写成插件

两者技术上没有区别,钩子注册在哪都会执行。真正的差别在生命周期:functions.php 属于主题,换主题代码就没了;插件属于站点,换主题照样生效。判断标准只有一条——这段逻辑和「显示」有关还是和「数据」有关。

位置 适合放什么 换主题后 风险
子主题 functions.php 布局调整、模板函数、样式脚本引入 需要重新迁移 写错直接白屏,连后台都进不去
自建插件 文章类型、字段、同步、接口 不受影响 插件停用后数据仍在,但功能消失
代码片段插件 临时补丁、一次性修复 不受影响 片段多了难以追踪来源

实践中的分法是:先用代码片段插件验证钩子确实生效,确认没问题再落到子主题或独立插件。functions.php 里写错一个分号,前台会白屏,后台也进不去,只能靠 FTP 或文件管理器把文件改回去——这也是建议把改动先放子主题、并且保留一份备份的原因。

八、两个会搞坏站点的坑

无限递归

在 save_post 回调里调用 wp_update_post(),会再次触发 save_post,形成死循环,表现为保存文章时转圈直到 504。判断方法很直接:打开 debug.log 看到同一个函数名连续刷几十行堆栈,就是递归。update_post_meta() 不会触发 save_post,所以能用 meta 解决就别改文章本身。标准写法是先移除再挂回。

function xk_sync_title( $post_id ) {
    remove_action( 'save_post', 'xk_sync_title' );
    wp_update_post( array(
        'ID'         => $post_id,
        'post_title' => trim( get_the_title( $post_id ) ),
    ) );
    add_action( 'save_post', 'xk_sync_title' );
}
add_action( 'save_post', 'xk_sync_title' );

污染 REST 与 AJAX 响应

挂在 init 或 wp_head 上的 echo 一旦在 REST 请求里执行,返回的 JSON 前面就会多出一段 HTML,前端解析直接失败,控制台报 Unexpected token <。判断条件用 if ( ! defined( 'REST_REQUEST' ) || ! REST_REQUEST ),或者更省事:所有输出都挂在 wp_footer,REST 请求根本不触发它。

钩子冲突导致的症状通常很怪,比如某段文字出现两次、价格算错、后台保存报错,这类问题可以按插件冲突的方法逐个停用定位。要系统性地写一个自己的插件,插件定制那篇给了目录结构和加载顺序。改主题时钩子和模板要一起看,主题定制里讲了子主题的继承规则。如果钩子里写错导致整站白屏,按白屏排查先关插件再定位文件。

INDUSTRIAL BELT

扎根中国制造产业带
懂工厂,才懂出海

从胶东半岛到珠三角,从长三角到中原腹地,每一片产业集群都有自己的出口节奏与买家画像。信科网络长期扎根产业一线,把工厂的产品语言翻译成海外采购商听得懂的搜索语言,让每一分投入都对着真实的询盘去。

lulong

卢龙外贸网站建设

卢龙县地处河北省东北部、隶属于秦皇岛市,总面积961平方公里,全县辖12个乡镇、548个行政村,总人口41.5万人,其中农业人口38万人。卢龙,历史悠久,人杰地灵。1988年,卢龙县被确定为全国对…

fengjie

奉节外贸网站建设

奉节县位于重庆市东部,地处四川盆地东端,地理坐标为东经109°1′17″—109°45′58″,北纬30°29′19″—31°22′33″。县境东邻巫山县,南界湖北省恩施土家族苗族自治州,西连云阳县,北接巫溪县。境内有…

xianxian

献县外贸建站

献县历史悠久,夏商时期属冀州,春秋时属燕国,战国时属燕、赵、秦。早在春秋战国时期。境内就形成了具有一定影响力的城市——乐成。汉高祖六年(公元前201年)置河间郡,汉文帝二年…

tongnan

潼南外贸建站

潼南县位于重庆市西北部最前沿。是川渝握手区腹地东西南北交通自然便捷走向的交汇点及区域几何中心,是成渝经济区的腹地中心,是重庆西部凸入川中腹地最远、毗邻四川县市最多的对外开放最前沿…

fuling

涪陵WordPress建站

重庆市涪陵区,邑枕长、乌两江,素为乌江流域物资集散地,有渝东南门户之称,是闻名遐迩的中国“榨菜之乡”。全区幅员面积2946平方公里,辖1个经济技术开发区、1个私营经济示范区和44个乡镇、街…

yunhe

运河WordPress建站

运河区,隶属于河北省沧州市,也是沧州市政府所在地,因京杭大运河贯穿全境而命名。运河区位于沧州市区西部,总面积117.7平方公里,人口30.9万(2013年),辖1乡1镇、6个街道办事处,63个行政…

wanzhou

万州Shopify建站

重庆市万州区地处三峡库区腹心,长江中上游结合部,因“万川毕汇、万商云集”而得名,是长江十大港口之一。全区幅员面积3457平方公里,辖52个镇乡、街道。总人口171.7万,在重庆40个区县中人口最…

hengshui

衡水Shopify建站

衡水市位于河北省东南部,东与沧州市和山东省德州市毗邻,西与石家庄市接壤,南与邢台市相连,北与保定市交界;南北长125.5公里,东西宽98.13公里,全市辖8县2市1区,总面积5815.4平方公里。衡…

yuzhong

渝中外贸推广

渝中区,位于长江上游地区、重庆大都市区,属重庆主城九区之一。地处在长江、嘉陵江交汇地带,由于两江环抱,形似半岛,又名渝中半岛。渝中区作为重庆市政治、经济、文化以及商贸流通中心,别…

haiyuan

海原外贸推广

位于中国宁夏回族自治区中南部。1991年底人口31.15万。回族为主,有汉、满、蒙古、东乡等民族。西汉属安定郡。清同治十三年(1874)设海城县。民国三年(1914)改海原县。地形复杂,多山地。年…

nanan

南岸Google SEO

南岸区,是重庆都市区(重庆主城区),重庆大都市区,地处重庆市西南部。南岸区是以城市为主的都市工业区、中央商务区、国际会展区、风景旅游区。南岸区东部、南部接巴南区,西濒九龙坡区、渝…

qingtongxia

青铜峡Google SEO

青铜峡市位于黄河上游宁夏平原中部,地势平坦,牛首、峡口两山对峙,黄河穿峡而过,山光水色相映,宛如开然铜镜,“青铜峡”由此而得名。地处西北内陆,东部季风区与西部干旱区域的交汇地带,属…

fucheng

阜城GEO优化

阜城县是河北省衡水市下辖的一个县。面积698平方千米,人口32万。邮政编码053700。阜城县人文荟萃.历史上出现过不少的学者名流,北齐的刘昼,北周的熊安生,民国初期的京剧著名表演艺术家荀慧生,…

jinfeng

金凤GEO优化

金凤区于2002年11月1日成立,是宁夏回族自治区首府银川市的市辖区之一,位于市区中部,东到唐徕渠,西到包兰铁路,南接永宁县,北临贺兰县,辖良田、丰登2个镇,上海西路、北京中路、长城中路…

shizhu

石柱谷歌SEM

石柱土家族自治县简称石柱县,位于重庆市东部、长江南岸、山峡库区腹心,是集少数民族自治县、三峡库区淹没县、国家扶贫工作重点县于一体的特殊县份。幅员面积3012平方公里,辖32个乡镇,总人…

zhongqing

重庆谷歌SEM

重庆,简称渝或巴,是中华人民共和国中西部唯一的直辖市、国家中心城市、超大城市、国际大都市,长江上游地区的经济、金融、科创、航运和商贸物流中心,西部大开发重要的战略支点、“一带一路”…

zhongwei

中卫Facebook广告

中卫地处黄河前套之首,位于宁夏回族自治区中西部、宁、甘、内蒙古三省区交界点上,辖中宁、海原两县和中卫城区,总面积16986.1平方公里,其中川区面积3681.1平方公里,占21.67%,山区面积1330…

beidaihe

北戴河Facebook广告

北戴河海滨地处河北省秦皇岛市中心的西部。是秦皇岛的城市区之一!受海洋气候的影响,夏无酷暑,冬无严寒,常年保持一级大气质量,没有污染,没有噪音,城市森林覆盖率54%,人均绿地630平方米…

xiushan

秀山Yandex推广

秀山土家族苗族自治县,位于重庆市东南部,武陵山脉中段,四川盆地东南缘的外侧,为川东南重门户。与湖南省、湖北省、贵州省毗邻,326国道和319国道贯穿全县。距长沙604公里、武汉656里、贵阳5…

wuxi

巫溪Yandex推广

巫溪县地处大巴山东段南麓的渝、陕、鄂三省(市)结合部分,东接湖北省神农架林区、竹溪县,南临重庆市奉节、巫山两县,西与重庆开县、云阳接壤,北连重庆城口县和陕西省镇坪县。  

shenzhou

深州TikTok推广

深州市旧称深州,上古为冀州大陆之域。春秋时为晋地。战国属赵。秦属钜鹿郡。汉为下博县。隋开皇十六年(公元五九六年),于安平置深州,以城西故深池得名。尔后迭经废置,州城屡迁。唐徙州治…

为什么一定要选懂产业带的出海服务商?建材丝网的欧洲建筑商看资质与交期,工业橡塑的中东工程商问耐温与工况,毛皮服饰的俄罗斯分销商算的是季节与毛利。买家不同,决策逻辑就不同;信科网络把这些差异拆进网站结构、关键词地图和投放策略里,用他们习惯的方式出现在他们搜索的地方。