The main reference on the subject is the open_in_new Theme Development handbook.
Splitting a layout into parts
The repeating top of the page goes into header.php and is included with:
<?php get_header(); ?>
The bottom of the page goes into footer.php:
<?php get_footer(); ?>
Required hook calls
Two calls must be present in every theme, otherwise plugins won’t be able to inject their styles and scripts.
Inside the head container:
<?php wp_head(); ?>
Right before the closing body tag:
<?php wp_footer(); ?>
Paths and site information
Get the URL of the theme directory:
echo get_template_directory_uri();
Usage example:
<img src="<?= get_template_directory_uri(); ?>/images/logo.png" alt="" />
Information about the site:
bloginfo( 'name' ); // prints the value
get_bloginfo( 'name' ); // returns the value
// Available parameters:
// name
// description
// url
// admin_email
// charset
// version
// language
// stylesheet_url
// stylesheet_directory
For the site URL prefer the dedicated function instead of bloginfo():
// Use this:
home_url();
// Instead of:
bloginfo( 'url' );
get_bloginfo( 'url' );
The page title
Always use wp_title() rather than printing a static title: it adds the contextual information for
404 pages, search results, archive pages and so on.
<title><?php wp_title(); ?></title>
// Prints: " » Page Title"
The first parameter is the separator, so passing an empty string removes it:
<title><?php wp_title( '' ); ?></title>
// Prints: " Page Title"
The variant I normally use — the site name and tagline on the front page, and the page title everywhere else:
<title><?= is_front_page()
? get_bloginfo( 'name' ) . ' - ' . get_bloginfo( 'description' )
: trim( wp_title( '', false ) ); ?></title>
The loop
The loop is the block that walks through the posts prepared by the main query:
<?php if ( have_posts() ) : while ( have_posts() ) : the_post(); ?>
...
<?php endwhile; endif; ?>
Functions available inside the loop:
<?php the_ID(); ?>
<?php the_permalink(); ?>
<?php the_title(); ?>
<?php the_author(); ?>
<?php the_time( 'F jS, Y' ); ?>
<?php the_content(); ?>
Modifying the loop
query_posts() replaces the main query before the loop runs:
query_posts( 'category_name=special_cat&posts_per_page=10' );
if ( have_posts() ) : while ( have_posts() ) : the_post();
...
endwhile; endif;
The arguments can also be passed as an array, which is easier to read:
query_posts( [ 'category_name' => $category->slug ] );
The full list of arguments is documented in the open_in_new WP_Query parameters .
Note that query_posts() overwrites the main query, so for any secondary listing on the page create
a separate WP_Query object instead.
The template hierarchy
Every page needs a template, and WordPress has a fixed list of file names it looks for. The template hierarchy answers a single question:
What template file will WordPress use when a certain type of page is displayed?
The open_in_new hierarchy diagram shows the whole resolution order.
A typical task
Say we have a page listing the team members, and each member also needs their own page, both with custom templates. There are two ways to solve it.
Option 1 — pages.
Create a page with the slug clients and a template for it named page-clients.php. Create an arbitrary
custom template, e.g. client.php, and select it when creating a page for each member. The listing page
can then output all members if they are made children of the current page.
Option 2 — a custom post type.
Register a separate post type (for example client) alongside posts and pages. The listing then lives in
archive-client.php and every member gets single-client.php automatically. This is usually the cleaner
option.
Checking the current template
is_front_page()
is_home()
Andrew Dorokhov