
In lesson 1 we learned that WordPress has a hierarchy of templates to choose from, depending on the situation, and the index.php is the final fallback. As for now our theme consists of the index.php only, which means that this template will be used at any situation, whether the user is on the front page, clicked a post or a category. It’s time to create more templates to our themes, most importantly the single post template with the comments template.
In this lesson we will look at the single post view (single.php), single page view (page.php), the comments template (comments.php), archive template (archive.php), the search result template (search.php), and finally the 404 Error template for whenever the user has reached an URL that doesn’t exist (404.php). It’s a pretty long post with codes for each template, but don’t be intimidated – in most of the templates we just copy the index.php, rename the file and makes some small modifications. These are the basic templates for a well-working theme, but there exist many more. You are free to create as many of WordPress templates as you wish, but the single post, single page, and comments templates are the important ones.
For all templates we will be creating in this lesson, except the comments template, we simply copy our existing index.php and modify it. We keep the loop code in all templates, except in the comments template, which can seem a little odd for the single post or page template. It’s WordPress responsibility to fill the array which we loop through with the right posts; such as posts in the chosen category, or a single post when we click on a post permalink.
I’ll show the full code in the three first templates; the single post, single page and comments template, but as for the rest I’ll only show the code for the parts I’ve modified. You just remember to copy the index.php, rename it, find the place I’m pointing to and write out the modifications.
Single post template
Whenever the reader clicks on a post permalink, WordPress will call up the single post template, which should display only the one post with comments and a comment form below, normally with the header, sidebar and footer. We must make sure we display the complete post (not the excerpt), and we could also show more information about the post we don’t find necessary or too cluttering to display on the front page, such as date, category, tags, and author information.
The following differencies from index.php will be made in our single post template: the post title is no longer a link (it’ll just go to itself), the full post is to be displayed, we remove the page navigation necessary for the front page, and we add a call to the comments template below the post (the template which we will create later in this lesson). In the design I want a box listing related posts inbetween the post and the comment section, but that’s not until later in this tutorial so we just leave an empty <div> in its place. Remember that the code for the loop is to be included, it is WordPress responsibility to give us only one post in the loop.
Copy the index.php and rename the copy single.php. Open single.php and modify it into:
single.php:
<?php get_header(); ?> <div id="content"> <?php if (have_posts()) : while (have_posts()) : the_post(); ?> <div class="post single-post"> <h3 class="posttitle" id="post-<?php the_ID(); ?>"><?php the_title(); ?></h3> <div class="postcontent"> <?php the_content(); ?> </div> <div class="content-band"> <span class="postmeta-category"><?php the_category(', '); ?></span> <span class="postmeta-time"><?php the_time("F jS, Y"); ?></span> </div> <div class="related-template"></div> <div class="comments-template"> <?php comments_template(); ?> </div> </div> <?php endwhile; ?> <?php else: ?> <div class="post"> <p>Sorry, no posts found.</p> </div> <?php endif; ?> </div> <?php get_sidebar(); ?> <?php get_footer(); ?>
We make sure we display the full post by calling the_content() without any parameters. The comments_template() is the WordPress function call to insert the comments template, which works just like get_header(), get_sidebar(), get_footer() or like the PHP include().
I normally skip using the post_class() on single post templates, because WordPress won’t differentiate single views from e.g. the frontpage. For instance if we style sticky posts differently to emphasize them on the front page, this will also reflect on the single post view, which s not always desirable. In this example I’ve given them the class post to inherit our default post styling, and added single-post in case we want to specifically target single post view posts.
WordPress Codex Function references:
- get_header, get_sidebar, and get_footer
- the_title
- the_ID
- the_content
- the_time
- the_category
- comments_template
Single page template
Our next template is single page template, which is basically the same as single posts except it’s used for pages. If the theme doesn’t have a single page template, WordPress will fall back into using the template we created above, single.php. I prefer to create a single page template because pages have some important differencies from posts. First and foremost, pages can be paginated and we need to add support for this. Also pages can’t be assigned tags or categories. And normally we skip displaying the date, author, and disallow comments on pages.
Create a copy of the index.php and rename the copy page.php. We then open page.php and make modifications:
page.php:
<?php get_header(); ?> <div id="content"> <?php if (have_posts()) : while (have_posts()) : the_post(); ?> <div class="post single-page"> <h3 class="posttitle" id="post-<?php the_ID(); ?>"><?php the_title(); ?></h3> <div class="postcontent"> <?php the_content(); ?> <?php wp_link_pages(array('before' => '<p><strong>Pages:</strong>', 'after' => '</p>')); ?> </div> </div> <?php endwhile; ?> <?php else: ?> <div class="post"> <p>Sorry, no posts found.</p> </div> <?php endif; ?> </div> <?php get_sidebar(); ?> <?php get_footer(); ?>
The wp_link_pages() is the support for paginated pages, displaying links to the next and previous pages of the page. I’ve also skipped including the comments template (comments_template()) like we did in single.php, but it’s up to you if you want to allow comments on pages. Otherwise the template is quite similar to single.php, except we dropped printing out category, tags, and date information. I’ve assigned single pages with the class single-page in case I want to style single page view differently.
WordPress Codex Function references:
- get_header, get_sidebar, and get_footer
- the_title
- the_ID
- the_content
- the_time
- the_category
- comments_template
- wp_link_pages
Comments template
The comments template is different from the other templates, as it’s included as a part of another template, in our case the single.php. The comments template contains both the list of comments based on the global post object and the comment form.
The code below is pretty standard for WordPress comment templates. It allows password-protecting comments, supports closed comments, comments from logged-in users only, avatars and gives the commenter information that the post is waiting for moderation if the author has chosen to approve comments before they are published. If the commenter is logged in the comment form will not ask for the name and e-mail but instead display the logged-in user information.
Since this template is so radically different than the other templates in WordPress, we are not to copy index.php for this one. Create a new empty file in your theme directory named comments.php and copy and paste the code below.
comments.php:
<?php // Do not delete these lines if ('comments.php' == basename($_SERVER['SCRIPT_FILENAME'])) die ('Please do not load this page directly. Thanks!'); if (!empty($post->post_password)) { // if there's a password if ($_COOKIE['wp-postpass_' . COOKIEHASH] != $post->post_password) { // and it doesn't match the cookie ?> <h2>Password Protected</h2> <p>Enter the password to view comments.</p> <?php return; } } /* Variable for alternating comment background */ $oddcomment = 'alt'; ?> <!-- You can start editing here. --> <?php if ($comments) : ?> <h3 id="comments"><?php comments_number('No Comments', '1 Comment', '% Comments');?></h3> <ol class="commentlist"> <?php foreach ($comments as $comment) : ?> <li class="<?php /* Separate admin comments */ if ($comment->user_id) echo 'authcomment'; else echo $oddcomment; ?>" id="comment-<?php comment_ID() ?>"> <?php // Get avatar image. If e-mail = gravatar, display that, otherwise display WP default avatar ?> <?php echo get_avatar(get_comment_author_email(), 64); ?> <div class="commentmetadata"> <span class="comment-author"><?php comment_author_link() ?></span> <span class="comment-date"><?php comment_date('F jS, Y') ?> at <?php comment_time() ?></span> <?php if ($comment->comment_approved == '0') : ?> <span class="comment-mod"><em>Your comment is awaiting moderation.</em></span> <?php endif; ?> </div> <div class="comment-text"> <?php comment_text() ?> </div> <div style="clear:both;"></div> </li> <?php /* Changes every other comment to a different class */ if ('alt' == $oddcomment) $oddcomment = ''; else $oddcomment = 'alt'; ?> <?php endforeach; /* end for each comment */ ?> </ol> <?php else : /* this is displayed if there are no comments so far */ ?> <h3 id="comments">No comments</h3> <?php if ('open' == $post->comment_status) : ?> <!-- If comments are open, but there are no comments. --> <p class="nocomments">No comments yet.</p> <?php else : // comments are closed ?> <!-- If comments are closed. --> <p class="nocomments">Comments are closed.</p> <?php endif; ?> <?php endif; ?> <?php if ('open' == $post->comment_status) : ?> <h3 id="respond">Leave a Comment</h3> <?php if ( get_option('comment_registration') && !$user_ID ) : ?> <p>You must be <a href="<?php echo get_option('siteurl'); ?>/wp-login.php?redirect_to=<?php the_permalink(); ?>">logged in</a> to post a comment.</p> <?php else : ?> <form action="<?php echo get_option('siteurl'); ?>/wp-comments-post.php" method="post" id="commentform"> <?php if ( $user_ID ) : ?> <p>Logged in as <a href="<?php echo get_option('siteurl'); ?>/wp-admin/profile.php"><?php echo $user_identity; ?></a>. <a href="<?php echo get_option('siteurl'); ?>/wp-login.php?action=logout" title="Log out of this account">Logout »</a></p> <?php else : ?> <p><input type="text" name="author" id="author" value="<?php echo $comment_author; ?>" size="40" tabindex="1" /> <label for="author"><small>Name <?php if ($req) echo "(required)"; ?></small></label></p> <p><input type="text" name="email" id="email" value="<?php echo $comment_author_email; ?>" size="40" tabindex="2" /> <label for="email"><small>Mail (will not be published) <?php if ($req) echo "(required)"; ?></small></label></p> <p><input type="text" name="url" id="url" value="<?php echo $comment_author_url; ?>" size="40" tabindex="3" /> <label for="url"><small>Website</small></label></p> <?php endif; ?> <p><textarea name="comment" id="comment" cols="60" rows="10" tabindex="4"></textarea></p> <p class="allowed-tags"><small><strong>XHTML: </strong>You can use these tags:<code><?php echo allowed_tags(); ?></code></small></p> <p><input name="submit" type="submit" id="submit" tabindex="5" value="Submit Comment" /> <input type="hidden" name="comment_post_ID" value="<?php echo $id; ?>" /> </p> <?php do_action('comment_form', $post->ID); ?> </form> <?php endif; // If registration required and not logged in ?> <?php endif; // if you delete this the sky will fall on your head ?>
To sum this template up, basically what this code does is to first check if the post has password-protected comments, and making sure the user can’t comment without writing the password, and opening up for comments if the correct password was given. Then we enter a loop of all comments belonging this post, and for each we extract the information we want, namely the avatar, name, date, and comment text. After the loop of comments and some alternate informative headlines, we print out the form for adding comments.
A few things to note here is: we’re giving each alternate comment a class alt, and comments from the blog’s author the class authcomment, so we can single them out and specifically style them with CSS. You are free to modify the date and time format to follow standards where you come from. You can also change the avatar size by giving the size in pixels as the second parameter to get_avatar() (in this example we use 64 by 64 pixels).
WordPress Codex Function references:
- comments_number
- get_avatar
- comment_author_email
- comment_author_link
- comment_date
- comment_time
- comment_text
- get_option
- the_permalink
- do_action
Search results template
This template, named search.php, is used when the reader clicks search next to the search field. We want to display a list of posts and pages which matches the given search words. What we want is very similar to what we use on the front page, except a explanatory title above the posts. You can also choose to display less of the post content text, using e.g. the_excerpt(), or create a different way of listing posts than what we do on the front page.
The code below takes care of displaying a title before the posts saying something in the lines of “Search results for …”. But we want the title after the test have_posts(), so it’s only displayed if there actually are any posts that matches the search criteria, otherwise it could be a little misguiding. So we split our Loop line in two, inserting the title inbetween:
<?php if (have_posts()) : ?> <h2>Search results for <?php the_search_query() ?></h2> <?php while (have_posts()) : the_post(); ?>
The WordPress function the_search_query() will return whatever the reader searched, giving the reader a more informative title. It’s also a good idea to add a different explanatory text for when the loop contains no posts, in the else part of the loop. The “Sorry, no posts found” I’ve used in these templates may be more helpful if we added something to help the reader find what she/he wants. For instance lists of posts, pages or a tag cloud.
WordPress Codex Function references:
Archive template
The archive template, named archive.php, is used whenever the reader has clicked something that logically should display posts matching whatever that has been chosen, such as a month, year, tag, category, or author. If you recall the hierarchy explained in lesson 1, we can create separate templates for each type of archive, whereas the archive.php is used as fallback.
Just like the search results template, we want to show a explanatory title above the posts to explain the reader what she/he’s looking at. There’s a bunch of WordPress functions we can use to determine the different types of archives, and just like the search results template we insert the title only if there are any posts, splitting the Loop line in two.
<?php if (have_posts()) : ?> <?php if (is_category()) { /* If this is a category archive */ ?> <h2>Posts filed under <em><?php single_cat_title(); ?></em></h2> <?php } elseif (is_tag()) { /* If this is a tag archive */ ?> <h2>Posts tagged with <em><?php single_tag_title(); ?></em></h2> <?php } elseif (is_day()) { /* If this is a daily archive */ ?> <h2>Archive for <?php the_time('F jS, Y'); ?></h2> <?php } elseif (is_month()) { /* If this is a monthly archive */ ?> <h2>Archive for <?php the_time('F, Y'); ?></h2> <?php } elseif (is_year()) { /* If this is a yearly archive */ ?> <h2>Archive for <?php the_time('Y'); ?></h2> <?php } elseif (isset($_GET['paged']) && !empty($_GET['paged'])) { /* If this is a paged archive */ ?> <h2>Blog Archives</h2> <?php } while (have_posts()) : the_post(); ?>
Basically we check every possible type of category and print out a suiting title.
WordPress Codex Function references:
404 Error template
Finally, the 404 Error template is displayed whenever the reader has reached or written an URL that doesn’t exist. As you should know by now, if the 404 template doesn’t exist in the theme, WordPress will fall back to using index.php with an empty loop. And with our index.php, the reader will get “Sorry, no posts found”. Not very informative is it?
The template, named 404.php, can be designed just as you like. You don’t need a Loop, and you can even skip the sidebar, and/or footer as well if you like. Maybe you’d rather display an image? Many people get creative with the 404 Error page, and so could you! For inspiration, see e.g. Smashing Magazine’s article with 404 Error page designs. What I normally do with my 404 Error templates is very similar to the search results template, I write an informative title and gives the reader some lists of posts or a tag cloud to further browse my site.
I won’t show any example code for this template, as it’s pretty short, simple and really up to you. You can make a copy of index.php, delete everything inside, but not including, the <div id="content"> and write whatever you want.
Templates – summing up
At this point we’ve created six templates (or less if you didn’t bother creating them all), all of which tells WordPress to do something different than our core index.php file in specific situations. Hopefully you’ve tested the templates and maybe made some modifications by your own preferences. You can add more templates if you’d like, refer to the visual representation of the WordPress hierarchy. For further reading on templates, see a list of them all at WordPress codex on templates.
We’ve now reached the point where we’ve covered most of our bases for when the reader clicks anything in our blog, but it’s becoming clear that we lack styling. We can finally begin styling the templates with CSS, now that we have more to style.
This concludes Lesson 5 of the WordPress Theme Tutorial. Go to the next lesson, Styling our Theme With CSS, or back to the tutorial’s Table of Contents.







Michael
July 29th, 2011 at 14:22
Hi. This is a great tutorial and I’m looking forward to the remaining parts.
In working my way through it, I believe there is a missing “?>” at the end of first line of the code you have for the archive.php template. (At least it didn’t work for me until I put that in.)
Cheers
Ann Christin Cappelen
July 29th, 2011 at 16:44
Thank you! You’re absolutely right, I’ve missed a ?> there. It’s now corrected.
Jasey
September 22nd, 2011 at 14:10
I’m a noob to WP but I’m trying to make my own template based on a PSD following your guide. Hope you find some time for explaining the rest of the lessons
I’ve got some questions, shouldn’t template pages start with:
< ?php
/*
Template Name: Search Page
*/
What do I need to make the search form work fine? I guess, I’ll need some PHP to do the query to the data base, it would be great if you could help me with that
Congrats for your work!! And thanks for your tuts!
Karuna
November 14th, 2011 at 12:51
Hi,
I’m getting an error on the archive.php file? Not sure why?
Karuna
November 14th, 2011 at 12:53
same error as well on the search.php file (Parse error: syntax error, unexpected $end in /home/livingsh/public_html/wp-content/themes/livingshaktitheme/search.php on line 3).
Both errors say unexpected error, I don’t write php so I’m not sure what the problem maybe?
Firefly
March 19th, 2012 at 00:17
I’m getting an error after using the search form too
( ! ) Parse error: syntax error, unexpected $end in C:\wamp\www\wp-content\themes\wpthemetutorial\search.php on line 3
Call Stack
# Time Memory Function Location
1 0.0011 365544 {main}( ) ..\index.php:0
2 0.0024 369296 require( ‘C:\wamp\www\wp-blog-header.php’ ) ..\index.php:17
3 0.3436 18038536 require_once( ‘C:\wamp\www\wp-includes\template-loader.php’ ) ..\wp-blog-header.php:16