New Functions, Hooks, and Behaviour for Theme Developers in WordPress 4.7

WordPress 4.7 introduces some new goodies for theme developers. These changes make powerful new functionality available to themes and plugins. It would be great to see theme developers testing this functionality and providing feedback here in the comments or on the individual tickets linked below.

The get_theme_file_uri() Function, and Friends

#18302

The get_template_part() function, introduced way back in WordPress 3.0, is a fundamental one to theme developers and child theming. The function looks in the child theme for the specified file, and falls back to the parent theme if the file doesn’t exist. This allows a template part to be easily overridden by a child theme, simply by means of the file existing.

The new get_theme_file_uri() function introduced in WordPress 4.7 enables this child theme behaviour for theme file URLs, for example when a CSS or JavaScript file is enqueued. Here’s an example of its use:

wp_enqueue_script( 'my-script', get_theme_file_uri( 'js/my-script.js' ) );

The above code enqueues the URL of the js/my-script.js file from the child theme if it exists, falling back to the URL of the file in the parent theme. Now your parent theme can enable each of its enqueued assets to easily be overridden by a child theme. And of course, if no child theme is in use then the function simply uses the parent theme URL, just like get_template_part().

The companion function get_theme_file_path() has also been introduced. This is the file path equivalent of get_theme_file_uri(). One use case for this function is if you like to dynamically generate the version parameter for your enqueued assets based on the last modified timestamp of the file (using filemtime()). You can continue to do so by using this function:

wp_enqueue_script(
	'my-script',
	get_theme_file_uri( 'js/my-script.js' ),
	array(),
	filemtime( get_theme_file_path( 'js/my-script.js' ) )
);

Finally, get_parent_theme_file_uri() and get_parent_theme_file_path() are also introduced, which specifically reference the file URL or file path to the file in the parent theme (and regardless of whether or not the file exists). For consistency, these functions can be used as replacements for when you may have otherwise used get_template_directory_uri() and get_template_directory() respectively.

The {$type}_template_hierarchy Filter

#14310

This new dynamically-named filter allows the complete template hierarchy of a given request type to be filtered by a plugin or theme. Although it’s technically possible for the template hierarchy to be filtered using the existing template_include filter, this new filter allows it to be done in a much more clean, simple, and future-proof way, and without the need to reimplement the entire hierarchy logic within the filter’s callback function.

The filter names available by default are:

  • embed_template_hierarchy
  • 404_template_hierarchy
  • search_template_hierarchy
  • frontpage_template_hierarchy
  • home_template_hierarchy
  • taxonomy_template_hierarchy
  • attachment_template_hierarchy
  • single_template_hierarchy
  • page_template_hierarchy
  • singular_template_hierarchy
  • category_template_hierarchy
  • tag_template_hierarchy
  • author_template_hierarchy
  • date_template_hierarchy
  • archive_template_hierarchy
  • paged_template_hierarchy
  • index_template_hierarchy

Here’s an example of the usage of this new filter to add a year-based file to the top of the hierarchy for date archives:

add_filter( 'date_template_hierarchy', function( array $templates ) {
	$year = get_query_var( 'year' );
	array_unshift( $templates, "year-{$year}.php" );
	return $templates;
} );

Here’s a slightly more complex example of adding a file to the hierarchy for a category archive based on the value of its term meta field:

add_filter( 'category_template_hierarchy', function( array $templates ) {
	$format = get_term_meta( get_queried_object_id(), 'format', true );
	if ( $format ) {
		$new = "category-format-{$format}.php";
		$pos = array_search( 'category.php', $templates );
		array_splice( $templates, $pos, 0, $new );
	}
	return $templates;
} );

More usage examples can be seen in the comment thread on the ticket: #14310.

This filter also allows debugging plugins to access and display the complete template hierarchy for each request, so you can see which files WordPress is looking for in your theme. The latest version of Query Monitor already supports this functionality.

Alert: It’s important to remember that the consistency of the template hierarchy in WordPress is what makes standardised theme structures possible. It’s highly recommended that you do not remove templates from the candidate hierarchy using these new filters, unless you’re absolutely certain of what you’re doing.

Simpler Template Names for Content with Non-ASCII Slugs

#37655

Given a post or term with a non-ASCII name, such as hello-world-😀, the URL-encoded form of the name is used in the template hierarchy. For example, on the single post view the hierarchy looked like this prior to WordPress 4.7:

  • single-post-hello-world-%f0%9f%98%80.php
  • single-post.php
  • single.php
  • singular.php
  • index.php

This isn’t very user-friendly, so WordPress 4.7 adds a new, higher priority template to the hierarchy which uses the non-encoded form of the name:

  • single-post-hello-world-😀.php
  • single-post-hello-world-%f0%9f%98%80.php
  • single-post.php
  • single.php
  • singular.php
  • index.php

This makes it much clearer what a template file refers to when building templates for specific posts or terms that include non-ASCII characters in their name.