* Utilities used to fetch and create templates and template parts.
// Define constants for supported wp_template_part_area taxonomy.
if ( ! defined( 'WP_TEMPLATE_PART_AREA_HEADER' ) ) {
define( 'WP_TEMPLATE_PART_AREA_HEADER', 'header' );
if ( ! defined( 'WP_TEMPLATE_PART_AREA_FOOTER' ) ) {
define( 'WP_TEMPLATE_PART_AREA_FOOTER', 'footer' );
if ( ! defined( 'WP_TEMPLATE_PART_AREA_SIDEBAR' ) ) {
define( 'WP_TEMPLATE_PART_AREA_SIDEBAR', 'sidebar' );
if ( ! defined( 'WP_TEMPLATE_PART_AREA_UNCATEGORIZED' ) ) {
define( 'WP_TEMPLATE_PART_AREA_UNCATEGORIZED', 'uncategorized' );
* For backward compatibility reasons,
* block themes might be using block-templates or block-template-parts,
* this function ensures we fallback to these folders properly.
* @param string $theme_stylesheet The stylesheet. Default is to leverage the main theme root.
* Folder names used by block themes.
* @type string $wp_template Theme-relative directory name for block templates.
* @type string $wp_template_part Theme-relative directory name for block template parts.
function get_block_theme_folders( $theme_stylesheet = null ) {
$theme = wp_get_theme( (string) $theme_stylesheet );
if ( ! $theme->exists() ) {
// Return the default folders if the theme doesn't exist.
'wp_template' => 'templates',
'wp_template_part' => 'parts',
return $theme->get_block_template_folders();
* Returns a filtered list of allowed area values for template parts.
* The allowed template part area values.
* Data for the allowed template part area.
* @type string $area Template part area name.
* @type string $label Template part area label.
* @type string $description Template part area description.
* @type string $icon Template part area icon.
* @type string $area_tag Template part area tag.
function get_allowed_block_template_part_areas() {
$default_area_definitions = array(
'area' => WP_TEMPLATE_PART_AREA_UNCATEGORIZED,
'label' => _x( 'General', 'template part area' ),
'General templates often perform a specific role like displaying post content, and are not tied to any particular area.'
'area' => WP_TEMPLATE_PART_AREA_HEADER,
'label' => _x( 'Header', 'template part area' ),
'The Header template defines a page area that typically contains a title, logo, and main navigation.'
'area' => WP_TEMPLATE_PART_AREA_FOOTER,
'label' => _x( 'Footer', 'template part area' ),
'The Footer template defines a page area that typically contains site credits, social links, or any other combination of blocks.'
* Filters the list of allowed template part area values.
* @param array[] $default_area_definitions {
* The allowed template part area values.
* Data for the template part area.
* @type string $area Template part area name.
* @type string $label Template part area label.
* @type string $description Template part area description.
* @type string $icon Template part area icon.
* @type string $area_tag Template part area tag.
return apply_filters( 'default_wp_template_part_areas', $default_area_definitions );
* Returns a filtered list of default template types, containing their
* localized titles and descriptions.
* The default template types.
* Data for the template type.
* @type string $title Template type title.
* @type string $description Template type description.
function get_default_block_template_types() {
$default_template_types = array(
'title' => _x( 'Index', 'Template name' ),
'description' => __( 'Used as a fallback template for all pages when a more specific template is not defined.' ),
'title' => _x( 'Blog Home', 'Template name' ),
'description' => __( 'Displays the latest posts as either the site homepage or as the "Posts page" as defined under reading settings. If it exists, the Front Page template overrides this template when posts are shown on the homepage.' ),
'title' => _x( 'Front Page', 'Template name' ),
'description' => __( 'Displays your site\'s homepage, whether it is set to display latest posts or a static page. The Front Page template takes precedence over all templates.' ),
'title' => _x( 'Single Entries', 'Template name' ),
'description' => __( 'Displays any single entry, such as a post or a page. This template will serve as a fallback when a more specific template (e.g. Single Post, Page, or Attachment) cannot be found.' ),
'title' => _x( 'Single Posts', 'Template name' ),
'description' => __( 'Displays a single post on your website unless a custom template has been applied to that post or a dedicated template exists.' ),
'title' => _x( 'Pages', 'Template name' ),
'description' => __( 'Displays a static page unless a custom template has been applied to that page or a dedicated template exists.' ),
'title' => _x( 'All Archives', 'Template name' ),
'description' => __( 'Displays any archive, including posts by a single author, category, tag, taxonomy, custom post type, and date. This template will serve as a fallback when more specific templates (e.g. Category or Tag) cannot be found.' ),
'title' => _x( 'Author Archives', 'Template name' ),
'description' => __( 'Displays a single author\'s post archive. This template will serve as a fallback when a more specific template (e.g. Author: Admin) cannot be found.' ),
'title' => _x( 'Category Archives', 'Template name' ),
'description' => __( 'Displays a post category archive. This template will serve as a fallback when a more specific template (e.g. Category: Recipes) cannot be found.' ),
'title' => _x( 'Taxonomy', 'Template name' ),
'description' => __( 'Displays a custom taxonomy archive. Like categories and tags, taxonomies have terms which you use to classify things. For example: a taxonomy named "Art" can have multiple terms, such as "Modern" and "18th Century." This template will serve as a fallback when a more specific template (e.g. Taxonomy: Art) cannot be found.' ),
'title' => _x( 'Date Archives', 'Template name' ),
'description' => __( 'Displays a post archive when a specific date is visited (e.g., example.com/2023/).' ),
'title' => _x( 'Tag Archives', 'Template name' ),
'description' => __( 'Displays a post tag archive. This template will serve as a fallback when a more specific template (e.g. Tag: Pizza) cannot be found.' ),
'title' => __( 'Attachment Pages' ),
'description' => __( 'Displays when a visitor views the dedicated page that exists for any media attachment.' ),
'title' => _x( 'Search Results', 'Template name' ),
'description' => __( 'Displays when a visitor performs a search on your website.' ),
'privacy-policy' => array(
'title' => __( 'Privacy Policy' ),
'description' => __( 'Displays your site\'s Privacy Policy page.' ),
'title' => _x( 'Page: 404', 'Template name' ),
'description' => __( 'Displays when a visitor views a non-existent page, such as a dead link or a mistyped URL.' ),
// Add a title and description to post format templates.
$post_formats = get_post_format_strings();
foreach ( $post_formats as $post_format_slug => $post_format_name ) {
$default_template_types[ 'taxonomy-post_format-post-format-' . $post_format_slug ] = array(
/* translators: %s: Post format name. */
_x( 'Post Format: %s', 'Template name' ),
'description' => sprintf(
/* translators: %s: Post format name. */
__( 'Displays the %s post format archive.' ),
* Filters the list of default template types.
* @param array[] $default_template_types {
* The default template types.
* Data for the template type.
* @type string $title Template type title.
* @type string $description Template type description.
return apply_filters( 'default_template_types', $default_template_types );
* Checks whether the input 'area' is a supported value.
* Returns the input if supported, otherwise returns the 'uncategorized' value.
* @param string $type Template part area name.
* @return string Input if supported, else the uncategorized value.
function _filter_block_template_part_area( $type ) {
$allowed_areas = array_map(
static function ( $item ) {
get_allowed_block_template_part_areas()
if ( in_array( $type, $allowed_areas, true ) ) {
$warning_message = sprintf(
/* translators: %1$s: Template area type, %2$s: the uncategorized template area value. */
__( '"%1$s" is not a supported wp_template_part area value and has been added as "%2$s".' ),
WP_TEMPLATE_PART_AREA_UNCATEGORIZED
wp_trigger_error( __FUNCTION__, $warning_message );
return WP_TEMPLATE_PART_AREA_UNCATEGORIZED;
* Finds all nested template part file paths in a theme's directory.
* @param string $base_directory The theme's file path.
* @return string[] A list of paths to all template part files.
function _get_block_templates_paths( $base_directory ) {
static $template_path_list = array();
if ( isset( $template_path_list[ $base_directory ] ) ) {
return $template_path_list[ $base_directory ];
if ( is_dir( $base_directory ) ) {
$nested_files = new RecursiveIteratorIterator( new RecursiveDirectoryIterator( $base_directory ) );
$nested_html_files = new RegexIterator( $nested_files, '/^.+\.html$/i', RecursiveRegexIterator::GET_MATCH );
foreach ( $nested_html_files as $path => $file ) {
$template_path_list[ $base_directory ] = $path_list;
* Retrieves the template file from the theme for a given slug.
* @param string $template_type Template type. Either 'wp_template' or 'wp_template_part'.
* @param string $slug Template slug.
* Array with template metadata if $template_type is one of 'wp_template' or 'wp_template_part',
* @type string $slug Template slug.
* @type string $path Template file path.
* @type string $theme Theme slug.
* @type string $type Template type.
* @type string $area Template area. Only for 'wp_template_part'.
* @type string $title Optional. Template title.
* @type string[] $postTypes Optional. List of post types that the template supports. Only for 'wp_template'.
function _get_block_template_file( $template_type, $slug ) {
if ( 'wp_template' !== $template_type && 'wp_template_part' !== $template_type ) {
get_stylesheet() => get_stylesheet_directory(),
get_template() => get_template_directory(),
foreach ( $themes as $theme_slug => $theme_dir ) {
$template_base_paths = get_block_theme_folders( $theme_slug );
$file_path = $theme_dir . '/' . $template_base_paths[ $template_type ] . '/' . $slug . '.html';
if ( file_exists( $file_path ) ) {
$new_template_item = array(
'type' => $template_type,
if ( 'wp_template_part' === $template_type ) {
return _add_block_template_part_area_info( $new_template_item );
if ( 'wp_template' === $template_type ) {
return _add_block_template_info( $new_template_item );
return $new_template_item;
* Retrieves the template files from the theme.
* @since 6.3.0 Added the `$query` parameter.
* @param string $template_type Template type. Either 'wp_template' or 'wp_template_part'.
* Arguments to retrieve templates. Optional, empty by default.
* @type string[] $slug__in List of slugs to include.
* @type string[] $slug__not_in List of slugs to skip.
* @type string $area A 'wp_template_part_area' taxonomy value to filter by (for 'wp_template_part' template type only).
* @type string $post_type Post type to get the templates for.
* @return array|null Template files on success, null if `$template_type` is not matched.
function _get_block_templates_files( $template_type, $query = array() ) {
if ( 'wp_template' !== $template_type && 'wp_template_part' !== $template_type ) {
$default_template_types = array();
if ( 'wp_template' === $template_type ) {
$default_template_types = get_default_block_template_types();
// Prepare metadata from $query.
$slugs_to_include = isset( $query['slug__in'] ) ? $query['slug__in'] : array();
$slugs_to_skip = isset( $query['slug__not_in'] ) ? $query['slug__not_in'] : array();
$area = isset( $query['area'] ) ? $query['area'] : null;
$post_type = isset( $query['post_type'] ) ? $query['post_type'] : '';
$stylesheet = get_stylesheet();
$template = get_template();
$stylesheet => get_stylesheet_directory(),
// Add the parent theme if it's not the same as the current theme.
if ( $stylesheet !== $template ) {
$themes[ $template ] = get_template_directory();
$template_files = array();
foreach ( $themes as $theme_slug => $theme_dir ) {
$template_base_paths = get_block_theme_folders( $theme_slug );
$theme_template_files = _get_block_templates_paths( $theme_dir . '/' . $template_base_paths[ $template_type ] );
foreach ( $theme_template_files as $template_file ) {
$template_base_path = $template_base_paths[ $template_type ];
// Starting position of slug.
strpos( $template_file, $template_base_path . DIRECTORY_SEPARATOR ) + 1 + strlen( $template_base_path ),
// Subtract ending '.html'.
// Skip this item if its slug doesn't match any of the slugs to include.
if ( ! empty( $slugs_to_include ) && ! in_array( $template_slug, $slugs_to_include, true ) ) {
// Skip this item if its slug matches any of the slugs to skip.
if ( ! empty( $slugs_to_skip ) && in_array( $template_slug, $slugs_to_skip, true ) ) {
* The child theme items (stylesheet) are processed before the parent theme's (template).
* If a child theme defines a template, prevent the parent template from being added to the list as well.
if ( isset( $template_files[ $template_slug ] ) ) {
$new_template_item = array(
'slug' => $template_slug,
'path' => $template_file,
'type' => $template_type,
if ( 'wp_template_part' === $template_type ) {
$candidate = _add_block_template_part_area_info( $new_template_item );
if ( ! isset( $area ) || ( isset( $area ) && $area === $candidate['area'] ) ) {
$template_files[ $template_slug ] = $candidate;
if ( 'wp_template' === $template_type ) {
$candidate = _add_block_template_info( $new_template_item );
$is_custom = ! isset( $default_template_types[ $candidate['slug'] ] );
( $post_type && isset( $candidate['postTypes'] ) && in_array( $post_type, $candidate['postTypes'], true ) )
$template_files[ $template_slug ] = $candidate;
// The custom templates with no associated post types are available for all post types.
if ( $post_type && ! isset( $candidate['postTypes'] ) && $is_custom ) {
$template_files[ $template_slug ] = $candidate;
return array_values( $template_files );
* Attempts to add custom template information to the template item.
* @param array $template_item Template to add information to (requires 'slug' field).
* @return array Template item.
function _add_block_template_info( $template_item ) {
if ( ! wp_theme_has_theme_json() ) {
$theme_data = wp_get_theme_data_custom_templates();
if ( isset( $theme_data[ $template_item['slug'] ] ) ) {
$template_item['title'] = $theme_data[ $template_item['slug'] ]['title'];
$template_item['postTypes'] = $theme_data[ $template_item['slug'] ]['postTypes'];
* Attempts to add the template part's area information to the input template.
* @param array $template_info Template to add information to (requires 'type' and 'slug' fields).
* @return array Template info.