搜索全站
主题 12 分钟阅读

自定义页面模板 Page Template:头部注释与后台选择

想给落地页、全宽页、归档页各做一套布局?讲清自定义页面模板的头部注释写法、子目录支持、限定文章类型、与 page-{slug}.php 的区别,以及后台下拉框不显示的排查。

U
UFO_PF 2016-10-28 · 阅读 12 分钟
自定 主题
阅读提示
本文约 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

常见踩坑

  1. 头部注释写成了普通 // 注释或位置不在文件开头。 必须是 /** */ 块注释且位于文件最前(前面不能有空行、BOM 或 <?php 之外的字符)。
  2. Template Name 拼错。 写成 Template: 或 Template name:(小写 n)都识别不了,核心匹配的是精确的 Template Name。
  3. 缓存导致后台不刷新。 修改头部注释后需要清缓存:wp cache flush 或后台「设置 → 固定链接」点一次保存也能触发部分缓存清理。
  4. 放在了两层子目录。 page-templates/special/landing.php 扫描不到,只能一层。
  5. 文件权限问题。 get_file_data() 读不到内容时直接跳过,没有任何报错,表现为”文件在但下拉框没有”。
  6. 案例: 某站把落地页模板命名为 template-landing.php,头部注释写的是 /* Template: 落地页 */。后台始终只有默认模板。排查时用 wp post meta get 才发现 _wp_page_template 一直是 default。改成标准的 /** Template Name: 落地页 */ 并清缓存后立即可选。另一个常见变体是:文件顶部先写了 <?php defined('ABSPATH') || exit; 再写注释——此时注释不在文件头,同样识别失败。正确顺序是 <?php 紧接着注释块。
  7. 与 page-{slug}.php 混用导致困惑。 给 about 页面建了 page-about.php,又在后台选了”全宽页面”,最终生效的是后台选的那个。要恢复自动匹配,把下拉框改回”默认模板”即可。两者的取舍原则见WordPress 模板层级图解;若想让分类页也套用类似布局,分类模板的写法见分类模板 category-slug.php 用法。
  8. 子主题想覆盖父主题的页面模板。 直接放同名路径文件即可,但父主题的模板已经指派给某些页面时,_wp_page_template 里存的是相对路径字符串,子主题同名文件会被自动优先加载。

总结

自定义页面模板的识别条件是硬的:文件最开头的 /** Template Name: xxx */ 块注释,一层子目录以内,需要限定类型再加 Template Post Type。把它与 page-{slug}.php 区分开——前者是”编辑在后台选”,后者是”系统按别名自动匹配”,且前者优先级更高。开发时统一放 page-templates/ 目录,改完注释记得 wp cache flush;需要动态控制可选列表时用 theme_page_templates;模板内专用逻辑用 is_page_template() 包裹。

U
UFO_PF作者

专注 WordPress 生态的实战作者,内容以「可复制、可验证」为第一原则。

留下你的想法

邮箱地址不会被公开。带 * 的为必填项。