问题场景
WordPress 首页默认会列出所有最新文章,如果你有些分类是"内部用"或者"日报"性质,不希望它们污染首页排版,就需要主题层面把这些分类的文章从首页 query 里排除掉。比如本站把每天的"60s 资讯"这种刷屏内容单独放一个分类,首页只显示长内容。

解决:pre_get_posts 过滤
WordPress 提供了 pre_get_posts 这个 hook,在查询执行前就介入,可以修改任意 query 的参数。这是处理这类需求的标准做法 —— 比"事后过滤渲染"高效得多,因为 SQL 查询本身就不会捞那些文章。
把下面代码放进主题的 functions.php 或者你的 mu-plugin:
// 首页排除指定分类
function exclude_category_home( $query ) {
if ( $query->is_home() && $query->is_main_query() ) {
$query->set( 'cat', '-17' ); // 你要排除的分类 ID,负数表示排除
}
return $query;
}
add_filter( 'pre_get_posts', 'exclude_category_home' );
核心是 cat 参数支持负数表示排除。多个分类用逗号分隔:'-17,-18,-23'。
两个守卫条件别漏
注意上面代码里这两行判断:
if ( $query->is_home() && $query->is_main_query() )
is_home() —— 只针对博客首页生效(不影响分类页、归档页、搜索页)。如果你站点首页是设置成静态页面而不是博客列表,要用 is_front_page()。
is_main_query() —— 极其关键!没这个判断,所有侧边栏小工具、相关文章、热门文章这类 secondary query 都会被你过滤,首页 footer 的"最新评论"也会受波及,后果就是页面上一堆"无内容"的小工具空块。
升级版:同时排除多个分类 + 排除 RSS
实际项目里通常要稍微复杂一点。比如:
- 首页排除"日报"和"通知"两个分类(ID 17 和 18)
- RSS feed 也要排(否则订阅者天天被刷屏)
- 站内搜索结果不排(用户可能就是搜这些内容)
function b2be_exclude_categories_from_listings( $query ) {
if ( is_admin() || ! $query->is_main_query() ) {
return;
}
// 首页 / 博客首页 / Feed 都过滤
if ( $query->is_home() || $query->is_front_page() || $query->is_feed() ) {
$excluded_cats = [ 17, 18 ];
$current = $query->get( 'category__not_in', [] );
$query->set( 'category__not_in', array_merge( $current, $excluded_cats ) );
}
}
add_filter( 'pre_get_posts', 'b2be_exclude_categories_from_listings' );
这里用了 category__not_in 而不是 cat:
cat只接受字符串,改起来要拼字符串,而且会覆盖之前的设置category__not_in是数组,可以 merge,更安全,不会覆盖别人(其他插件)已经设的过滤条件
怎么找分类 ID
WordPress 后台 → 文章 → 分类目录,hover 上去看链接,URL 里 tag_ID=17 那个 17 就是分类 ID。或者直接 WP-CLI:
wp term list category --fields=term_id,name,slug,count
顺便:从某个 widget 也想排除
主题/插件提供的"最新文章"、"热门文章"小工具往往用自己的 query,pre_get_posts 也会拦截,这时反而要小心 —— 上面那个 is_main_query() 守卫就是为这个准备的,小工具的 query 不是 main_query,所以不会被影响。
如果你想让某个小工具也排除某分类,得看这个小工具内部用的是 WP_Query 还是 get_posts。如果是常规的 WP_Query,加另一个 hook 单独处理:
// 例如某个"猜你喜欢"小工具,识别它的 query
add_filter( 'pre_get_posts', function( $query ) {
if ( $query->is_main_query() ) return;
// 判断这个 secondary query 是哪个小工具发的(通常通过 query vars)
if ( $query->get( 'widget_id' ) === 'related-posts' ) {
$query->set( 'category__not_in', [ 17, 18 ] );
}
});
更激进:直接在主题模板里 query 时就过滤
如果你不想动 pre_get_posts,主题首页模板里直接控制 query 也行:
// 主题的 home.php / front-page.php
<?php
$args = [
'posts_per_page' => 10,
'category__not_in' => [ 17, 18 ],
'ignore_sticky_posts' => false,
];
$query = new WP_Query( $args );
while ( $query->have_posts() ) : $query->the_post();
get_template_part( 'template-parts/content', get_post_format() );
endwhile;
wp_reset_postdata();
?>
不推荐这种做法,因为商业主题升级会覆盖你的模板修改。所有"配置式"的过滤,都尽量做在 pre_get_posts 里 + 放 mu-plugin,主题升级零冲突。
—— 别看了 · 2026