本文约 4,513 字,预计阅读 12 分钟。文中命令与配置均经过实测,可直接参考执行。
引言:做了个落地页模板,后台下拉框里找不到
给市场部做了个去掉页头页脚的落地页模板 landing.php,文件头写了注释,FTP 上传好了,后台编辑页面时右侧”模板”下拉框里只有”默认模板”一项。反复刷新、清缓存、换浏览器都没用。
自定义页面模板是”让编辑在后台自己选布局”的机制,与 page-{slug}.php 那种”靠别名自动匹配”完全是两回事。两者经常被人混为一谈,于是出现了”文件在但选不到”的困惑。
原理分析:两种”给页面换布局”的机制
| 机制 | 触发方式 | 适用场景 | 后台是否可见 |
|---|---|---|---|
page-{slug}.php / page-{id}.php |
按页面别名/ID 自动匹配 | 固定的某几个页面 | 不可见,自动生效 |
自定义页面模板(含 Template Name 头) |
用户在后台手动选择 | 可复用的布局,编辑自己挑 | 可见,出现在”模板”下拉框 |
page.php |
兜底 | 其余所有页面 | — |
自定义页面模板的识别不靠文件名,靠文件头部的注释:
<?php
/**
* Template Name: 全宽页面(无侧边栏)
* Template Post Type: page
*/
核心用 get_file_data() 解析这段注释——它只读取文件前 8192 字节,所以注释必须放在文件最开头,前面不能有任何输出、空行或 UTF-8 BOM。解析后,模板名与文件名的映射存在缓存里(自 WordPress 4.9 起缓存在 theme-scanned-themes 相关的缓存组),这也是”改了注释但后台不更新”的原因之一。
优先级链条:
后台选中的自定义页面模板 → page-{slug}.php → page-{id}.php → page.php → singular.php → index.php
注意:自定义页面模板优先级最高。一旦在后台选了某个模板,page-{slug}.php 就不会再生效,这个反直觉的顺序是很多困惑的来源。
保姆级实操
第 1 步:写一个标准自定义页面模板
<?php
/**
* Template Name: 落地页(无导航无页脚)
* Template Post Type: page
*
* @package Mimangfei
*/
get_header( 'landing' ); // 使用 header-landing.php,找不到会退回 header.php
?>
<main id="primary" class="site-main landing">
<?php
while ( have_posts() ) :
the_post();
?>
<article id="post-<?php the_ID(); ?>" <?php post_class( 'landing__article' ); ?>>
<div class="landing__content">
<?php the_content(); ?>
</div>
</article>
<?php
endwhile;
?>
</main>
<?php
get_footer( 'landing' );
头部注释规范:
| 字段 | 作用 | 备注 |
|---|---|---|
Template Name |
后台显示的名字 | 必需,缺了就不算页面模板 |
Template Post Type |
限定可用于哪些类型 | WP 4.7+,如 page, post, product |
Template Description |
部分主题框架会读取 | 非核心字段 |
第 2 步:限定模板可用于哪些文章类型
/**
* Template Name: 产品对比页
* Template Post Type: page, product
*/
加上 Template Post Type 后,这个模板会同时出现在”页面”和”产品”的编辑界面里;不写这一行时(WP 4.7 之后)默认只对 page 生效。想让它只对自定义类型生效,就只写那个类型名。
第 3 步:把模板放进子目录
WordPress 4.7 起支持把页面模板放在主题的一级子目录里:
my-theme/
├── page-templates/
│ ├── landing.php
│ ├── full-width.php
│ └── no-title.php
├── page.php
└── style.css
目录层级只支持一层,再深(page-templates/sub/x.php)就扫描不到了。官方主题二十系列用的是 page-templates/ 这个目录名,已经成为行业惯例。
第 4 步:用过滤器动态增删模板列表
想让模板只出现在特定条件下(比如只有管理员能用),或者给插件注入模板:
// 给当前主题追加一个模板(适合从插件提供模板)
add_filter( 'theme_page_templates', function ( $templates, $theme, $post ) {
if ( $post && 'page' === $post->post_type ) {
$templates['page-templates/squeeze.php'] = '转化页(含表单)';
}
return $templates;
}, 10, 3 );
// 移除不需要的选项(只对非管理员隐藏"全宽")
add_filter( 'theme_page_templates', function ( $templates ) {
if ( ! current_user_can( 'manage_options' ) ) {
unset( $templates['page-templates/full-width.php'] );
}
return $templates;
} );
键是相对于主题根目录的路径,值是后台显示名。这个过滤器只改”可选列表”,不负责渲染——实际加载路径仍由 get_page_template() 处理。
第 5 步:判断当前页面用了哪个模板
if ( is_page_template( 'page-templates/landing.php' ) ) {
// 落地页专用逻辑:去掉 body 上的某些类、加载专用脚本
add_filter( 'body_class', function ( $classes ) {
$classes[] = 'is-landing';
return $classes;
} );
}
is_page_template() 不传参数时返回”是否使用了任何自定义模板”;传路径时做精确匹配(路径相对于主题根目录)。
第 6 步:命令行核对
# 列出某页面当前使用的模板(_wp_page_template 这个 meta)
wp post meta get 42 _wp_page_template
# 直接指定
wp post meta update 42 _wp_page_template page-templates/landing.php
# 清空自定义模板缓存(改了头部注释后后台不刷新时用)
wp cache flush
常见踩坑
- 头部注释写成了普通
//注释或位置不在文件开头。 必须是/** */块注释且位于文件最前(前面不能有空行、BOM 或<?php之外的字符)。 Template Name拼错。 写成Template:或Template name:(小写 n)都识别不了,核心匹配的是精确的Template Name。- 缓存导致后台不刷新。 修改头部注释后需要清缓存:
wp cache flush或后台「设置 → 固定链接」点一次保存也能触发部分缓存清理。 - 放在了两层子目录。
page-templates/special/landing.php扫描不到,只能一层。 - 文件权限问题。
get_file_data()读不到内容时直接跳过,没有任何报错,表现为”文件在但下拉框没有”。 - 案例: 某站把落地页模板命名为
template-landing.php,头部注释写的是/* Template: 落地页 */。后台始终只有默认模板。排查时用wp post meta get才发现_wp_page_template一直是default。改成标准的/** Template Name: 落地页 */并清缓存后立即可选。另一个常见变体是:文件顶部先写了<?php defined('ABSPATH') || exit;再写注释——此时注释不在文件头,同样识别失败。正确顺序是<?php紧接着注释块。 - 与
page-{slug}.php混用导致困惑。 给about页面建了page-about.php,又在后台选了”全宽页面”,最终生效的是后台选的那个。要恢复自动匹配,把下拉框改回”默认模板”即可。两者的取舍原则见WordPress 模板层级图解;若想让分类页也套用类似布局,分类模板的写法见分类模板 category-slug.php 用法。 - 子主题想覆盖父主题的页面模板。 直接放同名路径文件即可,但父主题的模板已经指派给某些页面时,
_wp_page_template里存的是相对路径字符串,子主题同名文件会被自动优先加载。
总结
自定义页面模板的识别条件是硬的:文件最开头的 /** Template Name: xxx */ 块注释,一层子目录以内,需要限定类型再加 Template Post Type。把它与 page-{slug}.php 区分开——前者是”编辑在后台选”,后者是”系统按别名自动匹配”,且前者优先级更高。开发时统一放 page-templates/ 目录,改完注释记得 wp cache flush;需要动态控制可选列表时用 theme_page_templates;模板内专用逻辑用 is_page_template() 包裹。
