Developing WordPress Themes - Getting Started

WordPress themes define the structure (a.k.a. layout) and design (a.k.a. skins or styles) of your site.

You manage your themes at Appearance > Themes; here you can switch themes, search and install new themes.

There are many themes available already on the Theme Directory. These themes have been checked and inspected, and you can preview your site with the theme before switching.

A fresh WordPress installation comes with some default themes installed, such as Twenty Fifteen, Twenty Fourteen and Twenty Thirteen. So it's usually not necessary to create a theme from scratch, but rather modify the existing theme using Child Themes, which inherits the existing, parent theme. This article, however, aims to teach you how to develop your own WordPress theme from scratch.

The Barebone

All the themes are store inside the wp-content/themes/ directory, relative to your application root directory.

$ cd /path/to/app/wp-content/themes/
$ ls -alF
total 24
drwxr-xr-x 5 daniel www-data 4096 May 21 13:43 ./
drwxr-xr-x 6 daniel www-data 4096 May 18 17:43 ../
-rw-rw-r-- 1 daniel www-data   28 Jun  5  2014 index.php
drwxr-xr-x 7 daniel www-data 4096 May 19 13:24 twentyfifteen/
drwxr-xr-x 9 daniel www-data 4096 May  7 09:45 twentyfourteen/
drwxr-xr-x 8 daniel www-data 4096 May  7 09:45 twentythirteen/

As you might have expected, the default themes can be found here. If you go to http://example.com/wp-admin/themes.php (where example.com is your blog's domain), you will see the three themes.

So let's create a new directory with the name of our theme. We'll call ours brew.

$ cd /path/to/app/wp-content/themes/
$ mkdir brew

If we, again, go to http://example.com/wp-admin/themes.php, we can see that WordPress already detects our new theme, but says it's broken. That's because we haven't put anything in it.

A theme can be made up of many template files, but as an absolute minimum requirement, it only needs two:

  • style.css
  • index.php

Let's create them now.

$ touch style.css index.php

And now the theme shows up correctly.

But if you click on the 'Theme Details' button you'll find the theme has no information whatsoever.

These details are defined within the style.css file in the form of comments. The comments must be at the top of the stylesheet, also called the stylesheet header.

These are some of the header names recognized by default by WordPress:

  • Author
  • Author URI
  • Description
  • Domain Path
  • Status
  • Tags
  • Template
  • Text Domain
  • Theme Name
  • Theme URI
  • Version

So the top of our style.css might look something like this:

/*
Theme Name: Brew
Theme URI: http://wordpress.org/themes/brew
Author: Daniel Li <dan@danyll.com>
Author URI: http://danyll.com/
Description: The most awesomest theme ever
Version: 1.0
License: MIT
License URI: http://opensource.org/licenses/MIT
Tags: responsive-layout
Text Domain: brew
*/

The Tags header name specifies the name of tags users can filter using the 'Feature Filter' options in the Theme Directory.

The Text Domain header name specifies the textdomain for the theme, a feature which is required for i18n. Look at it as like a namespace for the translations. We will touch on this a lot more later; for now, just set it to the same as your theme name, but lowercase and with whitespaces removed.

Template Files

Template files are the PHP files called to generate the pages requested. index.php is an example of a template file. As you'll see later, there could be many more - category.php, search.php, single.php - all different templates to render different page types.

Which template is used to render a particular page depends on the template hierarchy, but we will get to that later.

A template is made up of static markup, template tags and conditional tags. Template tags provides dynamic content, while conditional tags controls what gets displayed.

Template Tags

Inside the template, we can use Template Tags to display dynamic information, such as the latest 10 blog posts or comments.

Don't let this jargon term fool you. Template tags are just WordPress-defined PHP functions for displaying dynamic content, wrapped inside <?php ?> tags.

For example, <?php get_header(); ?> will include the template file header.php into the current template, <?php the_title(); ?> will get the title for the current post.

Useful Template Tags

Plugins may need to inject scripts or stylesheets onto the page for them to work. To ensure they show up, you must put <?php wp_head(); ?> just before the closing </head> tag, and also <?php wp_footer(); ?> just before the closing </body> tag.

index.php

Let's create a very simple index.php template using the <?php wp_title(); ?> and <?php bloginfo('name'); ?> template tags.

The wp_title() function gets and displays the information to go into the <title> tags. For SEO purposes, it is very important that each page has a distinctive <title> value, having <title>Brew</title> for all pages is bad!

Thus, by using the <?php wp_title(); ?> template tag, this <title> value is generated dynamically based on the type of post or pages, the title of posts, or dates of archive pages; this ensure each page in your blog gets a unique title.

The bloginfo() function displays information about your site. We can pass arguments into the function, in our case, name, to get the name of our site. Other useful arguments include stylesheet_url, template_url, url etc. All valid arguments can be found in the reference.

All the functions that you can use inside template tags are listed in the Function Reference.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title><?php wp_title("-", true, "right"); ?> - <?php bloginfo('name'); ?></title>
    <?php wp_head(); ?>
</head>
<body>
    <h1><?php bloginfo('name'); ?></h1>
    <?php wp_footer(); ?>
</body>
</html>

Conditional Tag

While a template tag gets data dynamically from the database, conditional tags tests against a criterion, and returns either true or false. Using this, we can control the logic of the template.

At its heart, it is still just a WordPress-defined PHP function wrapped in <?php ?> tags. For example, to display a big logo only if it's at the front page, you'd use the <?php is_front_page(); ?> tag.

if ( is_front_page() ) {
  // Display the logo
} else {
  // Don't display the logo
}

Display Posts

The default installation comes with a default post, obligatorily named "Hello World!".

But since our template simply print out the blog name, the post won't show up if you navigate to http://127.0.1.1/hello-world/.

We can use the conditional tag <?php if (is_single()) : ?> to test whether we are on a (single) post page.

<?php if (is_single()) : ?>
    <p>You're on a post page</p>
<?php endif; ?>

Now we can distinguish between a single post and other pages using conditional tags.

You can find other conditional tags in the Codex.

The Loop

Next, we need to retrieve information for that post and display it. All page or post content are displayed inside a while loop, which is unnecessarily given the jargon term 'The Loop'

<?php if (is_single()) {
    if (have_posts()) {
        while (have_posts()) {
            the_post();
            the_content();
        }
    }
} ?>

By processing the URL and querying the database, the have_posts() function checks to see if there are any posts, or content in the page. If it does, it will return true.

the_post() would then get the next item in the collection of posts and advances the internal posts counter by one, until have_posts() runs out of posts and return false, ending The Loop.

After the_post(), subsequent (downstream) code now have access to the post's data using template tags such as the_content() and the_excerpt(), which will be available until they are overriden by the next the_post() call.

the_content() will display the content of the current post. There are many other such template tags.

Some template tags are specific for posts, and should only go inside The Loop.

<?php if ( have_posts() ) : while ( have_posts() ) : the_post(); ?>
    <!-- Functions and markup to be ran on each post -->
<?php endwhile; else: ?>
    <p><?php _e('Sorry, no posts matched your criteria.'); ?></p>
<?php endif; ?>

Many use the while : and endwhile syntax over using braces. This is just a style preference - they achieve the same thing.

This is because functions such as the_content(), the_excerpt(), next_post(), and previous_post() needs to be applied to a post, and the post's information is made available via the_post(). If you use these functions outside the loop, this information may not be available.

Incorporating what you've learnt about template tags and the loop, your index.php now might look something like this:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0"/>
    <title><?php wp_title("-", true, "right"); ?> - <?php bloginfo('name'); ?></title>
    <?php wp_head(); ?>
</head>
<body>
    <div class="content">
        <?php if (have_posts()) {
            while (have_posts()) {
                the_post();
                the_content();
            }
        }; ?>
    </div>
    <?php wp_footer(); ?>
</body>
</html>

Template Hierarchy

We can use conditional tags to specify different logic for different type of pages inside our index.php, but that can gets bloated pretty quickly.

It's also not very maintainable as developers working on different pages would need to work on the same file; changes by one developer will, inevitably, get overriden by another other.

Instead, WordPress allows you to separate the different logic for different pages into separate files, and WordPress will load the files according to a Template Hierarchy.

For example, we can take the is_single() block out of the index.php, and place it inside a single.php file.

If we navigate to a single post page, we will see the same results, which is good. But now we have better separation (and ultimately maintainability) of our codebase.

single.php

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title><?php wp_title("-", true, "right"); ?> - <?php bloginfo('name'); ?></title>
</head>
<body>
    <h1><?php bloginfo('name'); ?></h1>
    <?php if (have_posts()) {
        while (have_posts()) {
            the_post(); ?>
            <h2><?php the_title(); ?></h2>
            <small><?php the_time('F jS, Y') ?> <!-- by <?php the_author() ?> --></small>
            <div class="content">
                <?php the_content(); ?>
            </div>
            <?php edit_post_link('Edit','',''); ?>  
            <?php
        }
    } ?>

</body>
</html>

index.php

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title><?php wp_title("-", true, "right"); ?> - <?php bloginfo('name'); ?></title>
</head>
<body>
    <h1><?php bloginfo('name'); ?></h1>
</body>
</html>

Special Files

single.php is a special file name which WordPress automatically recognizes. There are many of such special filenames.

  • style.css - The main stylesheet
  • rtl.css - Included automatically if the website's text direction is right-to-left
  • index.php - The main template which must be present
  • comments.php - The comments template
  • front-page.php - The front page template
  • home.php - The home page template, which is not well-named, as it is really the post-index page
  • single.php - The single post template
  • single-{post-type}.php - The single post template used when a single post from a custom post type is queried
  • page.php - The page template
  • category.php - The category template
  • tag.php - The tag template
  • taxonomy.php - The term template
  • author.php - The author template
  • date.php - The date/time template
  • archive.php - The archive template
  • search.php - The search results template
  • attachment.php - Attachment template. Used when viewing a single attachment
  • image.php - Image attachment template. Used when viewing a single image attachment
  • 404.php - The 404 Not Found template. Used when WordPress cannot find a post or page that matches the query.

As you may have noticed, a page can have many templates applied to it. category.php, archive.php and index.php can all be applied to a category page.

What WordPress do is to load templates according to a predefined Template Hierarchy. So when viewing the category page, it will first search for the category.php template, if that file doesn't exist, try the archive.php file, and then as a final fallback - the index.php.

The template hierarchy for any static pages is:

  • Custom Page Template
  • page-{id}.php
  • page-{slug}.php
  • page.php
  • index.php

For the Front Page (the one accessed on http://example.com/), the hierarchy is the same as with normal pages, but front-page.php will be loaded if available.

For an interactive view of the entire template hierarchy, please see http://wphierarchy.com/

The index.php file acts as a fallback all pages and that's why every theme must have one. (Unless it's a child theme which can use the parent theme's index.php)

Custom Page Template

If you want to create a custom template for pages, different from others in the same group, you'd need to create a custom template.

For example, let's say you're lauching a competition and that page should be styled differently from the rest of the posts. You'd need to create a custom template.

Creating a custom template is extremely easy. All you have to do is add a PHP comment on the top of the template file. Also make sure the file name for the template does not clash with any of the special files listed above.

<?php
/**
 * Template Name: Pre-launch Page
 */
?>

This will create a custom template called 'Pre-launch Page'. We can then select this on the 'Page Attributes' section on the right.

Only when a custom template is not selected will the templates be loaded using the Template Hierarchy order. It also means that on any page where you're using custom templates, you don't need to worry about the Template Hierarchy.

Here's what our custom template might look like:

<?php
/**
 * Template Name: Pre-launch Page
 */
?>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0"/>
    <title><?php wp_title("-", true, "right"); ?> - <?php bloginfo('name'); ?></title>
    <?php wp_head(); ?>
</head>
<body>
    <div class="content">
        <?php if (have_posts()) {
            while (have_posts()) {
                the_post();
                the_content();
            }
        }; ?>
    </div>
    <?php wp_footer(); ?>
</body>
</html>

If you enjoyed this article, why not take a look at the rest of the series on Developing WordPress Themes!

comments powered by Disqus