Capstone — Building a Custom Theme With a Custom Post Type

WordPress Intermediate/Advanced

Chapter 10 · Capstone — Building a Custom Theme With a Custom Post Type

Nine chapters have built the pieces, one at a time. This capstone assembles them into one real, working theme — a portfolio site with a custom post type, a widget area, and a genuinely secured contact form — closing both this course and the full 20-chapter WordPress track.

1. Theme File Structure

/* Theme Name: Capstone Portfolio Author: Your Name Version: 1.0 Text Domain: capstone-portfolio */

style.css's header, index.php, and header.php/footer.php (correctly calling wp_head()/wp_footer()) follow exactly the structure Chapter 1 established.

2. The "Project" Custom Post Type & Taxonomy

<?php function register_project_post_type() { register_post_type( 'project', array( 'labels' => array( 'name' => 'Projects' ), 'public' => true, 'has_archive' => true, 'supports' => array( 'title', 'editor', 'thumbnail' ), 'show_in_rest' => true, ) ); register_taxonomy( 'project_type', 'project', array( 'labels' => array( 'name' => 'Project Types' ), 'hierarchical' => true, 'public' => true, ) ); } add_action( 'init', 'register_project_post_type' ); ?>

Directly from Chapter 4, with Chapter 6's show_in_rest included from the start — this project type is REST-accessible immediately, not bolted on later.

3. Displaying Projects — Template Hierarchy & The Loop

<!-- archive-project.php --> <?php get_header(); ?> <?php if ( have_posts() ) : ?> <?php while ( have_posts() ) : the_post(); ?> <h2><a href="<?php the_permalink(); ?>"><?php the_title(); ?></a></h2> <?php the_post_thumbnail( 'medium' ); // preserves width/height automatically ?> <?php endwhile; ?> <?php endif; ?> <?php get_footer(); ?>

Per Chapter 2's own hierarchy, this file is automatically selected for the projects archive with no further wiring required. the_post_thumbnail() deliberately preserves the width/height attributes Chapter 9 named as a real CLS protection.

4. A Widget Area

<?php function register_sidebar_area() { register_sidebar( array( 'name' => 'Portfolio Sidebar', 'id' => 'portfolio-sidebar', ) ); } add_action( 'widgets_init', 'register_sidebar_area' ); ?>

register_sidebar(), hooked to its own dedicated widgets_init action, defines the widget area WordPress Fundamentals 7 covered purely from the site-owner side — this is what actually makes a widget area exist for a theme to offer in the first place.

5. A Properly Secured Contact Form

<!-- the form, inside a template --> <form method="post"> <?php wp_nonce_field( 'contact_form_submit', 'contact_nonce' ); ?> <input type="text" name="contact_name"> <textarea name="contact_message"></textarea> <button type="submit">Send</button> </form>
<?php function handle_contact_form() { if ( ! isset( $_POST['contact_nonce'] ) || ! wp_verify_nonce( $_POST['contact_nonce'], 'contact_form_submit' ) ) { wp_die( 'Security check failed.' ); } $name = sanitize_text_field( $_POST['contact_name'] ); $message = sanitize_textarea_field( $_POST['contact_message'] ); wp_mail( get_option( 'admin_email' ), 'New contact form submission', esc_html( $name ) . ' wrote: ' . esc_html( $message ) ); } add_action( 'admin_post_nopriv_contact_form', 'handle_contact_form' ); add_action( 'admin_post_contact_form', 'handle_contact_form' ); ?>

Chapter 8's own two-part defense, fully assembled: the nonce is checked before anything else runs, and both the submitted values and the final email output are escaped. This is the single most security-sensitive piece of the entire capstone, and deliberately the one built with the most care.

6. Enqueuing the Theme's Own Assets

<?php function portfolio_theme_scripts() { wp_enqueue_style( 'portfolio-style', get_stylesheet_uri(), array(), '1.0' ); wp_enqueue_script( 'portfolio-script', get_template_directory_uri() . '/js/main.js', array( 'jquery' ), '1.0', true // in_footer — Chapter 9's own INP-relevant choice ); } add_action( 'wp_enqueue_scripts', 'portfolio_theme_scripts' ); ?>

Chapter Attribution

PieceSource chapter
Theme file structure, wp_head()/wp_footer()Chapter 1
archive-project.php selected automaticallyChapter 2
The Loop displaying projectsChapter 3
The "project" post type and "project_type" taxonomyChapter 4
Contact form handled via a hooked actionChapter 5
show_in_rest on the project post typeChapter 6
Properly enqueued styles/scripts with a jQuery dependencyChapter 7
Nonce verification and output escaping on the contact formChapter 8
Preserved thumbnail dimensions, footer-loaded scriptsChapter 9

Honest Scope Note

What this capstone deliberately doesn't cover
  • No WooCommerce/e-commerce — reserved for a still-outstanding, deliberately-not-yet-scoped third WordPress course
  • No Full Site Editing/block-theme implementation — this capstone builds a classic PHP-template theme, matching this course's own focus throughout
  • No automated testing or version-controlled deployment workflow
  • Production deployment itself is Setting Up a Web Server on Debian's own territory, not repeated here
A closing, honest note on real-world practice
A genuine production version of a theme like this would often be built as a child theme of an established base theme, per WordPress Fundamentals 5's own material, rather than entirely from scratch — this capstone builds from scratch deliberately, specifically to demonstrate every piece explicitly rather than to model the most efficient real-world starting point.

Hands-On Exercises

Exercise 1

Explain why the contact form's nonce check happens before the sanitize_text_field() calls, rather than after, referencing this chapter's own Chapter 8 material.

📄 View solution
Exercise 2

Explain why the_post_thumbnail() is specifically called out in this chapter as preserving width/height "automatically," and why that matters for this capstone's own Chapter 9 attribution.

📄 View solution
Exercise 3

Explain why this capstone's own closing note says a real production theme would likely be a child theme instead, and why the capstone deliberately doesn't build it that way.

📄 View solution

Chapter 10 Quick Reference — Course & Track Complete

  • A real theme combining theme anatomy, the template hierarchy, the Loop, a custom post type + taxonomy, the REST API, proper enqueuing, security hardening, and performance practice
  • The contact form is the single most security-sensitive piece — nonce check first, then sanitize input, then escape output
  • register_sidebar(), hooked to widgets_init, is what makes a widget area exist for a theme to offer
  • WooCommerce/e-commerce is deliberately out of scope — a plausible future third WordPress course, not yet planned in detail
  • This completes both WordPress Fundamentals and WordPress Intermediate/Advanced — the full 20-chapter track