WordPress 钩子索引
一份可搜索的 WordPress 动作和过滤器索引,收录开发者真正会用到的钩子——附上精确的 add_action/add_filter 签名、触发时机、可运行的示例,以及最容易坑到人的地方。
39 个钩子,每个都附有确切的 add_action / add_filter 签名、触发时机、可用示例,以及最容易让人栽跟头的地方。 其中 1 个被标记为未经核实——我们尚未对照 WordPress 源码核对它们的参数个数,与其把猜测当作事实呈现,我们宁愿把这一点标出来。
这份清单并不详尽,也无意假装详尽——WordPress 有成千上万个钩子。它收录的是大家实际会用到的那些。
在 WordPress 加载完成之后、但在发送任何头信息之前运行。这是注册自定义文章类型、分类法和重写规则的标准位置。
触发时机
在每次请求时触发,前台和后台都会,且在核心、插件和主题加载完毕之后。
签名
add_action( 'init', 'my_callback' );示例
add_action( 'init', function () {
register_post_type( 'book', array(
'public' => true,
'label' => 'Books',
'show_in_rest' => true, // required for the block editor
) );
} );易错点
init 会在每一次请求时运行,包括 AJAX、REST 和 cron。在这里做任何耗时操作(一次 HTTP 请求、一个繁重的查询)都会拖累每一次页面加载。另外:这里能拿到当前用户,但你不能输出任何内容——头信息虽然还没发送,但马上就要发送了。
在主题加载完成后运行一次。这是调用 add_theme_support()、add_image_size() 以及注册导航菜单的正确位置。
触发时机
在主题的 functions.php 加载之后、init 之前。
签名
add_action( 'after_setup_theme', 'my_callback' );示例
add_action( 'after_setup_theme', function () {
add_theme_support( 'post-thumbnails' );
add_theme_support( 'title-tag' );
add_image_size( 'card', 600, 400, true ); // true = hard crop
} );易错点
在 init 而不是这里注册图片尺寸,大多数情况下能凑巧生效,但在某些情况下会莫名其妙地失败。请用 after_setup_theme——它就是为此而生的。
在所有已启用的插件加载完成后运行一次。这是与其他插件交互的最早安全时机。
触发时机
在每个插件文件被引入之后、主题加载之前。
签名
add_action( 'plugins_loaded', 'my_callback' );易错点
这里还拿不到当前用户——wp_get_current_user() 无法可靠工作。如果你需要用户信息,请用 init。
在 WordPress 完全加载之后运行——插件、主题、init 和用户都已就绪。
触发时机
在 init 之后、当前用户设置完成之后。
签名
add_action( 'wp_loaded', 'my_callback' );在每次后台请求开始时运行。用于 register_setting()、权限检查和后台重定向。
触发时机
在每一次后台请求时触发。
签名
add_action( 'admin_init', 'my_callback' );易错点
它也会在 admin-ajax.php 请求上触发,而这些其实并不是真正的“后台页面”。如果你的回调假定存在一个页面,请用 wp_doing_ajax() 加以防护。
加载前台 CSS 和 JavaScript 的唯一正确位置。
触发时机
在前台页面加载时、head 渲染之前触发。
签名
add_action( 'wp_enqueue_scripts', 'my_callback' );示例
add_action( 'wp_enqueue_scripts', function () {
wp_enqueue_style(
'child-style',
get_stylesheet_uri(),
array( 'parent-style' ), // load AFTER the parent
wp_get_theme()->get( 'Version' ) // cache-bust on version change
);
} );易错点
尽管名字里只有 scripts,它同样负责样式(STYLES)——并不存在 wp_enqueue_styles 这个钩子,很多人白白浪费时间去找它。另外:它不会在后台触发(请用 admin_enqueue_scripts),也不会在区块编辑器中触发(请用 enqueue_block_editor_assets)。
在后台加载 CSS/JS。它会接收当前页面的钩子后缀,因此你可以只在某一个页面上加载资源。
触发时机
在后台页面加载时触发。
签名
add_action( 'admin_enqueue_scripts', 'my_callback' ); // receives $hook_suffix示例
add_action( 'admin_enqueue_scripts', function ( $hook ) {
if ( 'settings_page_my-plugin' !== $hook ) {
return; // do not load our JS on every admin screen
}
wp_enqueue_script( 'my-admin', plugins_url( 'admin.js', __FILE__ ), array(), '1.0', true );
} );易错点
不对 $hook 加以判断,是插件破坏无关后台页面的最常见原因。只在需要的地方加载资源,其他任何地方都不要加载。
仅向区块编辑器(Gutenberg)加载资源——不影响前台,也不影响后台的其他部分。
触发时机
在区块编辑器加载时触发。
签名
add_action( 'enqueue_block_editor_assets', 'my_callback' );向 <head> 中输出内容。用于 meta 标签和内联的关键 CSS。
触发时机
在前台的 <head> 内触发。
签名
add_action( 'wp_head', 'my_callback' );易错点
不要在这里加载脚本或样式——请用 wp_enqueue_scripts,让 WordPress 来处理依赖关系和去重。直接把 <script> 标签 echo 进 wp_head 会绕过这一切,这正是那么多网站会三次加载 jQuery 的原因。
在 </body> 之前输出内容。这是放置统计分析代码片段和延迟加载标记的合适位置。
触发时机
在前台页面的末尾触发。
签名
add_action( 'wp_footer', 'my_callback' );易错点
如果主题忘了调用 wp_footer(),管理工具栏会失效,任何在页脚加载脚本的插件也会失效,而且会被 wordpress.org 的主题审核直接拒绝。
在文章内容显示之前对其进行过滤。这是在文章前后追加标记的常用位置。
触发时机
每次调用 the_content() 时触发。
签名
add_filter( 'the_content', 'my_callback' ); // receives $content示例
add_filter( 'the_content', function ( $content ) {
if ( ! is_singular( 'post' ) || ! in_the_loop() || ! is_main_query() ) {
return $content; // <- the guard everyone forgets
}
return $content . '<p class="cta">Enjoyed this? Read more.</p>';
} );易错点
它运行的频率远超你的想象——在小工具里、在 REST 响应里、在某些插件生成摘要时,以及在归档页循环中每篇文章都会运行一次。如果不加 is_singular / in_the_loop / is_main_query 判断,你的行动号召会在博客首页出现十五次,还会跑进 RSS 订阅源里。这是 WordPress 中被误用最多的过滤器。
在显示之前过滤文章标题。
触发时机
每次调用 the_title() 或 get_the_title() 时触发。
签名
add_filter( 'the_title', 'my_callback', 10, 2 ); // receives $title, $post_id易错点
它也会对后台菜单、导航菜单以及文档 <title> 中的标题触发。不加条件地过滤它,会在你意想不到的地方把内容改名。
设置自动生成的摘要包含多少个单词(WORDS)。默认为 55。
触发时机
当 WordPress 为没有手动摘要的文章生成摘要时触发。
签名
add_filter( 'excerpt_length', 'my_callback' ); // receives $length (words)示例
add_filter( 'excerpt_length', function ( $length ) {
return 25;
}, 999 ); // high priority: many themes set this too, and last one wins易错点
它对设置了手动摘要(MANUAL)的文章没有任何影响——那些摘要会被原样使用。而且许多主题自己就设置了这个过滤器,所以你往往需要一个较高的优先级数值才能胜出。
更改追加在被截断摘要末尾的字符串。默认为 " […]"。
触发时机
当自动生成的摘要被截断时触发。
签名
add_filter( 'excerpt_more', 'my_callback' ); // receives $more在查询运行之前(BEFORE)修改它。这是改变归档页显示内容的正确方式——远胜于再写一个 WP_Query。
触发时机
在查询变量被解析之后、SQL 被构建之前。
签名
add_action( 'pre_get_posts', 'my_callback' ); // receives WP_Query $query (by reference)示例
add_action( 'pre_get_posts', function ( $query ) {
if ( is_admin() || ! $query->is_main_query() ) {
return; // <- both guards are mandatory
}
if ( $query->is_category() ) {
$query->set( 'posts_per_page', 12 );
}
} );易错点
它在后台(ADMIN)也会运行,而且对每一个(EVERY)查询都会运行,包括菜单、小工具和媒体库。省略 is_admin() 和 is_main_query() 判断,你就会悄无声息地改变后台文章列表的显示内容——这种 bug 真的很难回溯定位。另外请注意:直接修改 $query,不要返回任何值。
过滤查询原始 SQL 中的 WHERE 子句。
触发时机
在查询 SQL 组装过程中触发。
签名
add_filter( 'posts_where', 'my_callback', 10, 2 ); // receives $where, WP_Query $query易错点
你写的是原始 SQL。务必用 $query->is_main_query() 加以判断,任何用户输入都务必使用 $wpdb->prepare()。这是往 WordPress 网站里引入 SQL 注入漏洞的最快方式。
过滤查询找到的文章总数——分页正是据此计算的。
触发时机
在查询运行之后、分页计算之前。
签名
add_filter( 'found_posts', 'my_callback', 10, 2 ); // receives $found_posts, WP_Query $query设置一个像素阈值,超过该阈值时 WordPress 会缩小上传的图片,并提供一份 "-scaled" 副本来代替你的原图。默认 2560。
触发时机
在上传时、当 WordPress 判断一张图片是否“过大”时触发。
签名
add_filter( 'big_image_size_threshold', 'my_callback' ); // receives int $threshold示例
// Serve originals at full resolution — no -scaled copy.
add_filter( 'big_image_size_threshold', '__return_false' );
// Or just raise the ceiling:
add_filter( 'big_image_size_threshold', function () {
return 3840;
} );易错点
这是“图片上传到 WordPress 后看起来变模糊”的最常见原因,而几乎没人知道它的存在。你的原图仍在磁盘上——只是永远不会被提供而已。请注意它以最长边(LONGEST edge)为准,并且只影响该过滤器生效之后上传的图片。
过滤 WordPress 即将为某次上传生成的图片尺寸数组。移除(unset)某个尺寸即可阻止它被生成。
触发时机
在上传时、衍生图片文件被创建之前。
签名
add_filter( 'intermediate_image_sizes_advanced', 'my_callback', 10, 2 ); // receives $sizes, $metadata示例
add_filter( 'intermediate_image_sizes_advanced', function ( $sizes ) {
unset( $sizes['1536x1536'] );
unset( $sizes['2048x2048'] );
return $sizes;
} );易错点
移除主题实际会用到(USES)的尺寸,意味着 WordPress 会回退到完整尺寸的原图——这比你省下的磁盘空间更得不偿失。而且它只会阻止生成新(NEW)文件;已有的文件在你重新生成之前会一直保留。
设置 WordPress 重新编码上传图片时使用的 JPEG 质量。默认 82。
触发时机
每当 WordPress 写入 JPEG 文件时触发。
签名
add_filter( 'jpeg_quality', 'my_callback', 10, 2 ); // receives $quality, $context易错点
质量 82 对照片来说没问题,但对截图、logo 和纯色图形来说效果明显偏差——这些图有清晰的硬边缘,又没有照片噪点来掩盖压缩伪影。另外:wp_editor_set_quality 是更现代的过滤器,直接覆盖图像编辑器类;两个都设置是常见做法。
设置 WP_Image_Editor 类(GD 和 Imagick)使用的输出质量。
触发时机
当图像编辑器实例设置其质量时触发。
签名
add_filter( 'wp_editor_set_quality', 'my_callback', 10, 2 ); // receives $quality, $mime_type把你的自定义图片尺寸添加到媒体弹窗和区块编辑器的尺寸下拉菜单中。
触发时机
当媒体界面构建其尺寸选择器时触发。
签名
add_filter( 'image_size_names_choose', 'my_callback' ); // receives $sizes示例
add_filter( 'image_size_names_choose', function ( $sizes ) {
return array_merge( $sizes, array( 'card' => __( 'Card', 'textdomain' ) ) );
} );易错点
add_image_size() 只是注册了尺寸,但并不会(NOT)让它在编辑器中可供选择。每一个“我的自定义图片尺寸没出现在下拉菜单里”的问题,都是缺了这个过滤器。
在文件被移入上传目录之前(BEFORE)对其进行检查或拒绝。
触发时机
在上传过程中、文件被写入之前。
签名
add_filter( 'wp_handle_upload_prefilter', 'my_callback' ); // receives $file易错点
要拒绝一个文件,请把 $file['error'] 设为一段提示信息字符串并返回 $file。返回 false 或抛出异常都不会达到你想要的效果。
在附件处理完成后过滤其元数据数组(包括已生成的各种尺寸)。
触发时机
在上传时衍生图片创建完成之后。
签名
add_filter( 'wp_generate_attachment_metadata', 'my_callback', 10, 2 ); // receives $metadata, $attachment_id易错点
这是图片优化类插件所使用的钩子。它可能很慢——它在每一种图片尺寸写入之后运行,如果在这里发起 HTTP 请求,会让每一次上传都被它拖住等待。
过滤 WordPress 为响应式图片生成的 srcset 候选项。
触发时机
当响应式图片被渲染时触发。
签名
add_filter( 'wp_calculate_image_srcset', 'my_callback', 10, 5 );易错点
Removing the medium_large (768w) size elsewhere silently degrades srcset — that size exists almost entirely to serve it.
我们尚未对照 WordPress 源码核实这个钩子确切的参数个数。在依赖它之前,请先查阅 developer.wordpress.org。与其悄悄把猜测当作事实呈现,我们宁愿把这份不确定标出来。
注册后台菜单和子菜单页面。
触发时机
在后台菜单构建时触发。
签名
add_action( 'admin_menu', 'my_callback' );示例
add_action( 'admin_menu', function () {
add_options_page(
'My Plugin',
'My Plugin',
'manage_options', // capability — NOT a role
'my-plugin',
'my_render_settings_page'
);
} );易错点
capability 参数是一项权限(CAPABILITY),不是角色。传入 "administrator" 看似能用(因为在某些配置里管理员恰好拥有同名的权限),却会悄悄把访问权授予不该拥有它的人。请用 manage_options。
在后台页面顶部输出一条通知。
触发时机
在后台页面渲染时触发。
签名
add_action( 'admin_notices', 'my_callback' );易错点
它在每一个(EVERY)后台页面上都会触发。有的插件不加条件地在所有页面上显示“感谢安装!”通知,这是 WordPress 后台里最招人厌的做法之一。请加上条件判断,并让它可以被关闭。
每当文章被创建或更新时运行。这是保存 meta box 数据的常用位置。
触发时机
在文章被写入数据库之后。
签名
add_action( 'save_post', 'my_callback', 10, 3 ); // receives $post_id, $post, $update示例
add_action( 'save_post', function ( $post_id ) {
if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) return;
if ( wp_is_post_revision( $post_id ) ) return;
if ( ! current_user_can( 'edit_post', $post_id ) ) return;
// ... now it is safe to save
}, 10, 1 );易错点
它在自动保存(AUTOSAVES)和修订版本(REVISIONS)时也会触发,而不只是真正的保存。如果不加上面那三处判断,每当编辑器自动保存时,你的元数据都会被空值覆盖——这种 bug 看起来就像数据在随机消失。
在文章编辑页面上注册 meta box。
触发时机
在编辑页面构建时触发。
签名
add_action( 'add_meta_boxes', 'my_callback', 10, 2 ); // receives $post_type, $post易错点
经典 meta box 不会出现在区块编辑器中,除非区块编辑器回退到兼容模式。对于原生使用区块编辑器的网站,应改为用 show_in_rest 注册文章元数据,并构建一个侧边栏面板。
在用户成功登录之后运行。
触发时机
在身份验证成功时触发。
签名
add_action( 'wp_login', 'my_callback', 10, 2 ); // receives $user_login, WP_User $user在新用户创建之后立即运行。
触发时机
在注册时触发。
签名
add_action( 'user_register', 'my_callback', 10, 2 ); // receives $user_id, $userdata过滤身份验证的结果。返回一个 WP_Error 即可阻止登录。
触发时机
在登录尝试过程中触发。
签名
add_filter( 'authenticate', 'my_callback', 30, 3 ); // receives $user, $username, $password易错点
在这里优先级非常重要。WordPress 核心把自己的检查挂在优先级 20 和 30 上——挂得太早,你就会在密码都还没被检查之前就运行了。
注册 REST 路由和字段。
触发时机
在 REST API 初始化时触发。
签名
add_action( 'rest_api_init', 'my_callback' );示例
add_action( 'rest_api_init', function () {
register_rest_route( 'myplugin/v1', '/items', array(
'methods' => 'GET',
'callback' => 'my_get_items',
'permission_callback' => '__return_true', // BE DELIBERATE ABOUT THIS
) );
} );易错点
permission_callback 是必需的(REQUIRED)——自 WP 5.5 起省略它会抛出一条通知,而把它设为 __return_true 会让该端点完全公开。对于真正的公开数据这没问题,但对于其他任何数据都是一个严重的漏洞。要做出明确决定,别用默认。
为 wp_schedule_event() 添加一个自定义的重复间隔。
触发时机
当 WordPress 构建其可用计划任务列表时触发。
签名
add_filter( 'cron_schedules', 'my_callback' ); // receives $schedules示例
add_filter( 'cron_schedules', function ( $schedules ) {
$schedules['every_five_minutes'] = array(
'interval' => 300, // seconds
'display' => __( 'Every 5 minutes', 'textdomain' ),
);
return $schedules;
} );易错点
WP-Cron 并不是真正的 cron。它只在有人访问网站时才触发,所以一个流量很低的网站并不会每五分钟就真的运行一次“五分钟计划任务”。如果时间精度真的很重要,请禁用 WP-Cron(DISABLE_WP_CRON),并从真正的系统 cron 中调用 wp-cron.php。
在评论被保存之后立即运行。
触发时机
在评论提交时触发。
签名
add_action( 'comment_post', 'my_callback', 10, 3 ); // receives $comment_id, $approved, $commentdata在评论数据被保存之前对其进行过滤。用于垃圾评论检查和校验。
触发时机
在评论被写入之前。
签名
add_filter( 'preprocess_comment', 'my_callback' ); // receives $commentdata易错点
要在这里拒绝一条评论,请带上提示信息调用 wp_die()——返回 false 并不能阻止它。
在结账成功后的订单接收页(order-received)上运行。
触发时机
当顾客进入感谢页面时触发。
签名
add_action( 'woocommerce_thankyou', 'my_callback' ); // receives $order_id易错点
这里并不是(NOT)触发订单履行的可靠位置。它只有在顾客真正到达感谢页面时才触发——如果顾客付款后关掉了标签页,它就永远不会运行。对于任何必须发生的操作,请使用 woocommerce_payment_complete 或订单状态变更钩子。
在商品页面上“加入购物车”按钮的正前方输出标记。
触发时机
在商品页面渲染过程中触发。
签名
add_action( 'woocommerce_before_add_to_cart_button', 'my_callback' );在经典结账页上添加、移除或重新排序字段。
触发时机
在结账表单构建时触发。
签名
add_filter( 'woocommerce_checkout_fields', 'my_callback' ); // receives $fields易错点
它只影响经典(CLASSIC,基于短代码的)结账页。较新的基于区块的结账页会完全忽略它,需要通过 JavaScript 中的 Slot & Fill 来定制。几乎每个人第一次都会在这里栽跟头。