SponsoredHosinger Special Offer

WP WordPress Theme Development

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.

Search Results and 404 Templates

WordPress Theme Development Lesson 23 of 30 ~4 min read

Overview

Two templates handle visitors who are looking for something: search.php shows the results of the site search, and 404.php handles URLs that do not exist — and both should help the visitor take a next step rather than leaving them stuck.

search.php

<?php
get_header();
?>

<main id="main" class="site-main">

    <header class="page-header">
        <h1 class="page-title">
            <?php
            printf(
                /* translators: %s: the search query */
                esc_html__( 'Search results for: %s', 'gdc-starter' ),
                '<span>' . esc_html( get_search_query() ) . '</span>'
            );
            ?>
        </h1>
    </header>

    <?php if ( have_posts() ) : ?>
        <?php while ( have_posts() ) : the_post(); ?>
            <?php get_template_part( 'template-parts/content', 'search' ); ?>
        <?php endwhile; ?>
        <?php the_posts_pagination(); ?>
    <?php else : ?>
        <p><?php esc_html_e( 'Nothing matched your search. Try different keywords.', 'gdc-starter' ); ?></p>
        <?php get_search_form(); ?>
    <?php endif; ?>

</main>

<?php
get_footer();
Watch out: The search query is typed by the visitor, so it is untrusted. get_search_query() escapes it by default, but always wrap it in esc_html() when printing — a search for <script> must show as text.

A custom searchform.php

Every call to get_search_form() — in your templates, in the Search widget — uses searchform.php if your theme has one:

<?php $unique_id = wp_unique_id( 'search-form-' ); ?>
<form role="search" method="get" class="search-form" action="<?php echo esc_url( home_url( '/' ) ); ?>">
    <label for="<?php echo esc_attr( $unique_id ); ?>" class="screen-reader-text">
        <?php esc_html_e( 'Search for:', 'gdc-starter' ); ?>
    </label>
    <input type="search"
           id="<?php echo esc_attr( $unique_id ); ?>"
           class="search-field"
           name="s"
           value="<?php echo get_search_query(); ?>"
           placeholder="<?php esc_attr_e( 'Search…', 'gdc-starter' ); ?>">
    <button type="submit" class="search-submit"><?php esc_html_e( 'Search', 'gdc-starter' ); ?></button>
</form>
  • The input must be named s — that is the query variable WordPress searches with.
  • wp_unique_id() gives each form on the page a different id, so two search forms never share one label.
  • get_search_query() with no argument is already escaped for use in an attribute.

404.php

<?php
get_header();
?>

<main id="main" class="site-main error-404">
    <header class="page-header">
        <h1 class="page-title"><?php esc_html_e( 'That page can’t be found.', 'gdc-starter' ); ?></h1>
    </header>

    <div class="page-content">
        <p><?php esc_html_e( 'It may have moved, or the link may be wrong. Try a search, or start from one of these:', 'gdc-starter' ); ?></p>

        <?php get_search_form(); ?>

        <h2><?php esc_html_e( 'Recent posts', 'gdc-starter' ); ?></h2>
        <ul>
            <?php
            wp_get_archives(
                array(
                    'type'  => 'postbypost',
                    'limit' => 5,
                )
            );
            ?>
        </ul>
    </div>
</main>

<?php
get_footer();
Tip: WordPress already sends a real 404 status code with this template, so search engines drop the dead URL. Do not redirect every 404 to the home page — that tells search engines the broken URL is valid.

Practice

  • Build search.php and search for a word, then for <b>test</b>, and confirm it prints as text.
  • Add searchform.php and check the search widget uses it.
  • Visit /this-does-not-exist/ and confirm your 404.php shows.
Create a free account to save which lessons you've finished. Save my progress