Himanshu SharmaSoftware engineer
Let’s talk

WordPress · Lesson 20 of 45

Templates and template parts

Add templates for single posts, pages, archives, search and 404 pages to a block theme, register template part areas and custom page templates in theme.json.

14 minBlock themesUpdated 23 October 2026

By the end of this lessonGive each kind of page its own layout, organise shared parts, and offer editors a choice of page templates.

One template is not enough

The Bakery theme from Your first block theme has a single index.html template, so a blog post, a page and a category archive all look the same: a list of posts. Real sites need different layouts. WordPress chooses a template for each request by looking for the most specific file first, then falling back (the template hierarchy lesson covers the full order). For a block theme the common templates are:

Template Used for
front-page.html the site's front page
home.html the blog posts page
single.html a single post
page.html a single page
archive.html category, tag, author and date archives
search.html search results
404.html "not found" pages
index.html everything without a more specific template

A single post template

A post page should show one post in full: title, date, content and comments.

html
<!-- wp:template-part {"slug":"header","tagName":"header"} /-->

<!-- wp:group {"tagName":"main","layout":{"type":"constrained"}} -->
<main class="wp-block-group">
	<!-- wp:post-title {"level":1} /-->
	<!-- wp:post-date /-->
	<!-- wp:post-content {"layout":{"type":"constrained"}} /-->
	<!-- wp:comments -->
	<div class="wp-block-comments">
		<!-- wp:comments-title /-->
		<!-- wp:comment-template -->
			<!-- wp:comment-author-name /-->
			<!-- wp:comment-content /-->
		<!-- /wp:comment-template -->
		<!-- wp:post-comments-form /-->
	</div>
	<!-- /wp:comments -->
</main>
<!-- /wp:group -->

<!-- wp:template-part {"slug":"footer","tagName":"footer"} /-->

No Query Loop is needed here: on a single post page, blocks such as Post Title and Post Content read the current post directly.

http
GET /hello-world/
result
Hello world!
January 15, 2026
Welcome to WordPress. This is your first post. Edit or delete it, then start writing!
One response to “Hello world!”
A WordPress Commenter
Hi, this is a comment.
To get started with moderating, editing, and deleting comments, please visit the Comments screen in the dashboard.
Commenter avatars come from Gravatar.
Leave a Reply Cancel reply
Your email address will not be published. Required fields are marked *
Comment *
Name *
Email *
Website
Save my name, email, and website in this browser for the next time I comment.

The page shows the post's title as a heading, its date (formatted with the site's date format), the content, and the comments section with the example comment and the reply form.

Pages and archives

A page usually needs no date or comments:

html
<!-- wp:template-part {"slug":"header","tagName":"header"} /-->
<!-- wp:group {"tagName":"main","layout":{"type":"constrained"}} -->
<main class="wp-block-group">
	<!-- wp:post-title {"level":1} /-->
	<!-- wp:post-content {"layout":{"type":"constrained"}} /-->
</main>
<!-- /wp:group -->
<!-- wp:template-part {"slug":"footer","tagName":"footer"} /-->

An archive needs to say what it is listing. The Query Title block shows "Category: Recipes" or "Tag: vegan" automatically:

html
<!-- wp:template-part {"slug":"header","tagName":"header"} /-->
<!-- wp:group {"tagName":"main","layout":{"type":"constrained"}} -->
<main class="wp-block-group">
	<!-- wp:query-title {"type":"archive"} /-->
	<!-- wp:term-description /-->
	<!-- wp:query {"query":{"inherit":true}} -->
	<div class="wp-block-query">
		<!-- wp:post-template -->
			<!-- wp:post-title {"isLink":true,"level":2} /-->
		<!-- /wp:post-template -->
		<!-- wp:query-pagination -->
			<!-- wp:query-pagination-previous /-->
			<!-- wp:query-pagination-next /-->
		<!-- /wp:query-pagination -->
	</div>
	<!-- /wp:query -->
</main>
<!-- /wp:group -->
<!-- wp:template-part {"slug":"footer","tagName":"footer"} /-->
http
GET /category/uncategorized/
result
Category: Uncategorized
Hello world!

A friendly 404 page

When an address does not exist, WordPress uses 404.html. Make it helpful: say what happened and offer a search box.

html
<!-- wp:template-part {"slug":"header","tagName":"header"} /-->
<!-- wp:group {"tagName":"main","layout":{"type":"constrained"}} -->
<main class="wp-block-group">
	<!-- wp:heading {"level":1} --><h1 class="wp-block-heading">Page not found</h1><!-- /wp:heading -->
	<!-- wp:paragraph --><p>Sorry, we could not find that page. Try a search, or go back to the home page.</p><!-- /wp:paragraph -->
	<!-- wp:search {"label":"Search","buttonText":"Search"} /-->
</main>
<!-- /wp:group -->
<!-- wp:template-part {"slug":"footer","tagName":"footer"} /-->
http
GET /no-such-page/
result
Page not found
Sorry, we could not find that page. Try a search, or go back to the home page.
Search
Search
http
GET /no-such-page/
result
HTTP/1.1 404 Not Found

The response status is a real 404 Not Found, which matters for search engines: a "not found" page must not answer with 200 OK, or search engines may index it as a real page.

Declaring parts and custom templates

Template parts work without being declared, but declaring them in theme.json gives each a readable title and an area (header, footer or uncategorized), which the Site Editor uses to group and label them. customTemplates adds templates that editors can choose for individual pages or posts. If you followed the previous lesson, add the two keys to your existing theme.json; here is a complete minimal version:

json
{
	"$schema": "https://schemas.wp.org/trunk/theme.json",
	"version": 3,
	"settings": { "layout": { "contentSize": "680px", "wideSize": "1100px" } },
	"templateParts": [
		{ "name": "header", "title": "Header", "area": "header" },
		{ "name": "footer", "title": "Footer", "area": "footer" }
	],
	"customTemplates": [
		{ "name": "page-no-title", "title": "Page without title", "postTypes": [ "page" ] }
	]
}
html
<!-- wp:template-part {"slug":"header","tagName":"header"} /-->
<!-- wp:post-content {"layout":{"type":"constrained"},"tagName":"main"} /-->
<!-- wp:template-part {"slug":"footer","tagName":"footer"} /-->

WordPress now knows all of the theme's templates and parts:

php
<?php
foreach ( wp_list_sort( get_block_templates(), 'slug' ) as $template ) {
	printf( "template  %-14s %s\n", $template->slug, $template->title );
}
foreach ( wp_list_sort( get_block_templates( [], 'wp_template_part' ), 'slug' ) as $part ) {
	printf( "part      %-14s area: %s\n", $part->slug, $part->area );
}
result
template  404            Page: 404
template  archive        All Archives
template  index          Index
template  page           Pages
template  page-no-title  Page without title
template  single         Single Posts
part      footer         area: footer
part      header         area: header

Assign the custom template to a page, as the editor's Template setting does, and that page uses it:

php
<?php
update_post_meta( 2, '_wp_page_template', 'page-no-title' );
echo get_page_template_slug( 2 ), "\n";
result
page-no-title
http
GET /sample-page/
result
<main id="wp--skip-link--target" class="entry-content wp-block-post-content is-layout-constrained wp-block-post-content-is-layout-constrained">

The page has no h1 now: the custom template has no Post Title block (and the header's Site Title is a paragraph, as set up in the first theme lesson). Its main element is the Post Content block itself, as its classes show.

Common mistakes

  • Duplicating whole layouts in every template instead of sharing header and footer parts (and patterns, next lesson).
  • A 404 template that returns to the home page without saying anything; visitors and search engines need a clear "not found".
  • Forgetting {"inherit":true} on archive Query Loops, so every archive shows the same posts.
  • Custom templates without postTypes, which then appear only for pages by default.

Exercise

Add a search.html template that shows the search terms in a heading (Query Title with "type":"search"), the results as linked titles with excerpts, and a search box. Request /?s=hello to test it.

Show one possible answer
html
<!-- wp:template-part {"slug":"header","tagName":"header"} /-->
<!-- wp:group {"tagName":"main","layout":{"type":"constrained"}} -->
<main class="wp-block-group">
	<!-- wp:query-title {"type":"search"} /-->
	<!-- wp:search {"label":"Search","buttonText":"Search"} /-->
	<!-- wp:query {"query":{"inherit":true}} -->
	<div class="wp-block-query">
		<!-- wp:post-template -->
			<!-- wp:post-title {"isLink":true,"level":2} /-->
			<!-- wp:post-excerpt /-->
		<!-- /wp:post-template -->
		<!-- wp:query-no-results --><!-- wp:paragraph --><p>No results.</p><!-- /wp:paragraph --><!-- /wp:query-no-results -->
	</div>
	<!-- /wp:query -->
</main>
<!-- /wp:group -->
<!-- wp:template-part {"slug":"footer","tagName":"footer"} /-->

/?s=hello shows a heading such as "Search results for: “hello”" followed by "Hello world!".

Key points

  • Templates live in the theme's templates folder; WordPress picks the most specific one for each request, falling back to index.html.
  • Common templates are single, page, archive, search, 404, home and front-page.
  • theme.json's templateParts names each part and its area (header, footer or uncategorized).
  • customTemplates offers extra page templates that editors can choose per page or post.

Check yourself

1. Which template shows a single blog post if the theme has it?
2. What does the Query Title block show on a category archive?
3. What is a custom template in a block theme?
4. Why declare template parts in theme.json?