Build classic and block WordPress themes from scratch: the template hierarchy, The Loop, the_title() and the_content(), template parts, menus, the Customizer, child themes and theme.json.
Lessons
When a plugin registers a custom post type such as book, your theme can give it its own look by adding single-book.php and archive-book.php — the template hierarchy picks them up automatically.
Remember the rule from lesson 1: the post type itself is registered in a plugin. The theme only provides templates for it. If the theme is switched, the books survive and simply fall back to single.php.
| Visitor sees | Template |
|---|---|
One book: /books/the-hobbit/ | single-book.php |
All books: /books/ | archive-book.php (needs has_archive set when the type is registered) |
Books in a genre: /genre/fantasy/ | taxonomy-genre.php |
<?php
/**
* Displays a single Book.
*/
get_header();
?>
<main id="main" class="site-main book">
<?php while ( have_posts() ) : the_post(); ?>
<?php
// Custom fields saved by the books plugin.
$isbn = get_post_meta( get_the_ID(), '_gdc_isbn', true );
$author = get_post_meta( get_the_ID(), '_gdc_book_author', true );
$pages = (int) get_post_meta( get_the_ID(), '_gdc_pages', true );
?>
<article id="post-<?php the_ID(); ?>" <?php post_class( 'book__article' ); ?>>
<div class="book__layout">
<div class="book__cover">
<?php the_post_thumbnail( 'medium_large' ); ?>
</div>
<div class="book__details">
<?php the_title( '<h1 class="book__title">', '</h1>' ); ?>
<dl class="book__facts">
<?php if ( $author ) : ?>
<dt><?php esc_html_e( 'Author', 'gdc-starter' ); ?></dt>
<dd><?php echo esc_html( $author ); ?></dd>
<?php endif; ?>
<?php if ( $pages ) : ?>
<dt><?php esc_html_e( 'Pages', 'gdc-starter' ); ?></dt>
<dd><?php echo esc_html( number_format_i18n( $pages ) ); ?></dd>
<?php endif; ?>
<?php if ( $isbn ) : ?>
<dt><?php esc_html_e( 'ISBN', 'gdc-starter' ); ?></dt>
<dd><?php echo esc_html( $isbn ); ?></dd>
<?php endif; ?>
</dl>
<?php the_terms( get_the_ID(), 'genre', '<p class="book__genres">' . esc_html__( 'Genres: ', 'gdc-starter' ), ', ', '</p>' ); ?>
<div class="entry-content"><?php the_content(); ?></div>
</div>
</div>
</article>
<?php endwhile; ?>
</main>
<?php
get_footer();get_post_meta( $id, $key, true ) — the third argument true returns a single value. Without it you get an array.if ( $author ) checks keep empty rows out of the page.esc_html() for text, esc_url() for links. Field values come from users and must never be printed raw.number_format_i18n() formats numbers for the site language — 1,234 in English, 1.234 in German.<?php
get_header();
?>
<main id="main" class="site-main">
<header class="page-header">
<h1 class="page-title"><?php post_type_archive_title(); ?></h1>
</header>
<?php if ( have_posts() ) : ?>
<div class="book-grid">
<?php while ( have_posts() ) : the_post(); ?>
<?php get_template_part( 'template-parts/content', 'book' ); ?>
<?php endwhile; ?>
</div>
<?php the_posts_pagination(); ?>
<?php else : ?>
<?php get_template_part( 'template-parts/content', 'none' ); ?>
<?php endif; ?>
</main>
<?php
get_footer();Do not run a new query in the template. Adjust the main query before it runs with pre_get_posts:
<?php
function gdc_starter_books_per_page( $query ) {
if ( is_admin() || ! $query->is_main_query() ) {
return;
}
if ( $query->is_post_type_archive( 'book' ) ) {
$query->set( 'posts_per_page', 12 );
$query->set( 'orderby', 'title' );
$query->set( 'order', 'ASC' );
}
}
add_action( 'pre_get_posts', 'gdc_starter_books_per_page' );Watch out: Always guardpre_get_postswithis_admin()andis_main_query(). Without them you change every query on the site — including dashboard lists and sidebar widgets.
book post type (the Plugin Development path shows how), then create single-book.php.get_post_meta(), escaping each one.pre_get_posts.In accordance with Section 6 & Section 7 of the DPDP Act 2023, third-party analytics and marketing trackers are strictly gated behind your affirmative consent. You may withdraw consent at any time under Section 6(4).
Essential for session authentication (ci_session), Cross-Site Request Forgery security (csrf_cookie_name), and Cloudflare DDoS/bot prevention. Cannot be disabled.
Gated under DPDP Section 7. Allows Google Analytics 4 (Google Consent Mode v2) to measure aggregate, anonymized tool usage.
Enables browser local storage to preserve your theme selection (dark/light mode) and code editor layout between visits.
Used to display developer sponsorships and native tool partner notices.