Custom Theme Development WordPress - Documentation


πŸ› οΈ Introduction to Custom Theme Development - WordPress

A WordPress theme controls the visual presentation of your website, including layout, styles, and templates for different types of content.

A custom theme means creating your own theme from scratch (or a starter theme) instead of modifying an existing one. This gives you full control over every aspect.

🧱 Setting Up the Theme

Step 1: Create Theme Directory

/your-site/wp-content/themes/my-custom-theme

πŸ“‚ Step 2: Basic Directory Structure (Recommended)

my-custom-theme/
β”‚
β”œβ”€β”€ assets/
β”‚   β”œβ”€β”€ css/
β”‚   β”œβ”€β”€ js/
β”‚   └── images/
β”‚
β”œβ”€β”€ template-parts/
β”‚   └── content-newsletter.php // can be used as multiple times once called using: get_template_part('template-parts/content', 'newsletter_temp');
β”‚
β”œβ”€β”€ templates/
β”‚   β”œβ”€β”€ home.php // can be used for templates for the static pages
β”‚   β”œβ”€β”€ about.php
β”‚   └── contact.php
β”‚
β”œβ”€β”€ style.css 
β”œβ”€β”€ functions.php
β”œβ”€β”€ index.php 
β”œβ”€β”€ header.php
β”œβ”€β”€ footer.php
β”œβ”€β”€ single.php // default blog (post) type - Detail page
β”œβ”€β”€ single-project.php // custom post type - Detail page
β”œβ”€β”€ page.php // fallback for static pages
β”œβ”€β”€ 404.php 
β”œβ”€β”€ search.php
β”œβ”€β”€ taxonomy-project-category-all.php // for taxonomy detail page
β”œβ”€β”€ taxonomy-project-category-commercial.php
β”œβ”€β”€ screenshot.png // for Theme Thumbnail image in WP Dashboard

πŸ“ style.css – Theme Metadata

Every theme must have a style.css with this block at the top:

The code should be commented at the Top

/*
Theme Name: My Custom Theme
Theme URI: https://example.com // Not Required
Author: Your Name
Author URI: https://example.com // Not Required
Description: A custom theme built from scratch.
Version: 1.0 
License: GNU General Public License v2 or later // Not Required
Text Domain: my-custom-theme // Not Required
*/

Even if you don’t add styles here, this file is required for the theme to be recognized by WordPress.

πŸ—‚οΈ Template Files & Hierarchy

βœ… How WordPress Detects Page Templates in Subfolders

WordPress will detect a page template in a subfolder like /template/ only if:

  1. The file has the proper header comment at the top: (BEST PRACTICE for Pages)
  2. It's located anywhere within your theme, including subdirectories. (Not Recommended)

As long as this file has the Template Name: comment, it will appear in the Page Template dropdown when editing a page in the admin.

🧠 Why It Works for Page Templates (But Not Others)?

Template Type Works in Subfolder? Notes
single.php, single-project.php ❌ No Must be in theme root
page.php, archive.php ❌ No Must be in theme root
Page Template (with Template Name) βœ… Yes Can be in /template/ folder
Partials (used with get_template_part()) βœ… Yes Manually included

Minimum Template: index.php

index.php is Served as a fallback when no specific template matches.

>>> If viewing a single post:

single-post.php
β†’ single.php
β†’ singular.php
β†’ index.php

- page.php is for Pages only.
- single.php is for Posts (and custom post types if single-{type}.php is not defined).
- singular.php is a universal fallback for any single item (page, post, or CPT).
- Then at last its index.php when no specific template matches.

πŸ“ Additional Notes

You don’t need to create every file β€” WordPress will always fall back safely to index.php.

πŸ“„ Working With Static Pages

/your-site/wp-content/themes/my-custom-theme/templates/home.php 

Define Template name at the top of the page and use get_header() method to get the header and get_footer() to get the footer in the page.

eg: about.php

<?php
/**
 * Template Name: About Us Page
 */
get_header();
?>

<--  Code Block Goes Here... -->

<?php
get_footer();
?>

Then inside the wordpress (Dashboard > pages > About us) page we can select the Template and publish the page

πŸ“’ Important Functions for functions.php

These are some common and usefull functions when developing any site from scratch

Added Excerpt to Default WP Post

function add_excerpt_to_default_post() {
    add_post_type_support('post', 'excerpt');
}
add_action('init', 'add_excerpt_to_default_post');

Embed the SVG type media in the backend

function cc_mime_types($mimes) {
    $mimes['svg'] = 'image/svg+xml';
    return $mimes;
}
add_filter('upload_mimes', 'cc_mime_types');

Register Menus

function notandas_register_menus() {
    register_nav_menu('primary', 'Primary Menu');
}
add_action('after_setup_theme', 'notandas_register_menus');

Disable Default Custom Styling of CF7

add_filter('wpcf7_load_css', '__return_false');

Enable support for featured images

add_theme_support('post-thumbnails');

Add a new Custom Post Type 'Projects'

function register_projects_custom_post_type() {
    $labels = [
        'name'                  => 'Projects',
        'Post type general name',
        'textdomain',
        'singular_name'         => 'Project',
        'Post type singular name',
        'textdomain',
        'menu_name'             => 'Projects',
        'Admin Menu text',
        'textdomain',
        'name_admin_bar'        => 'Project',
        'Add New on Toolbar',
        'textdomain',
        'add_new'               => 'Add New',
        'textdomain',
        'add_new_item'          => 'Add New Project',
        'textdomain',
        'new_item'              => 'New Project',
        'textdomain',
        'edit_item'             => 'Edit Project',
        'textdomain',
        'view_item'             => 'View Project',
        'textdomain',
        'all_items'             => 'All Projects',
        'textdomain',
        'search_items'          => 'Search Projects',
        'textdomain',
        'not_found'             => 'No projects found!',
        'textdomain',
        'not_found_in_trash'    => 'No projects found in Trash.',
        'textdomain',
    ];

    $args = [
        'labels'             => $labels,
        'public'             => true,
        'has_archive'        => true,
        'rewrite'            => ['slug' => 'projects'],
        'show_in_rest'       => true, // enable Gutenberg editor and REST API
        'supports'           => ['title', 'editor', 'thumbnail', 'excerpt', 'custom-fields'],
        'menu_position'      => 5,
        'menu_icon'          => 'dashicons-portfolio',
        // 'taxonomies'         => ['category'],
    ];

    register_post_type('project', $args);
}
add_action('init', 'register_projects_custom_post_type');

Shorthand of Register Post Type:

Also we can customize it as per our need

function register_book_post_type() {
     register_post_type('book', [
         'labels' => [
             'name' => 'Books',
             'singular_name' => 'Book',
         ],
        'public' => true,
         'has_archive' => true,
         'supports' => ['title', 'editor', 'thumbnail'],
         'rewrite' => ['slug' => 'books'],
         'show_in_rest' => true,
     ]);
 }
 add_action('init', 'register_book_post_type');

πŸ‘‰ Example Usecase of a header.php File

<!DOCTYPE html>
<html lang="en">

<head>

    <!-- List all the links of css files here at the Top -->
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="shortcut icon" href="" type="image/x-icon" />
    <title>Welcome to Notandas Realty</title>
    <link rel="stylesheet" href="<?php echo get_template_directory_uri(); ?>/css/bootstrap.min.css">
    <link rel="stylesheet" href="<?php echo get_template_directory_uri(); ?>/css/swiper-bundle.min.css">
    <link href="https://unpkg.com/aos@2.3.1/dist/aos.css" rel="stylesheet">
    <link rel="stylesheet" href="<?php echo get_template_directory_uri(); ?>/css/style.css">
    <link rel="stylesheet" href="<?php echo get_template_directory_uri(); ?>/css/animation.css">
    <link rel="stylesheet" href="<?php echo get_template_directory_uri(); ?>/css/innerpage.css">
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.2/css/all.min.css" integrity="sha512-Evv84Mr4kqVGRNSgIGL/F/aIDqQb7xQ2vcrdIwxfjThSH8CSR7PBEakCr51Ck+w+/U6swU2Im1vVX0SVk9ABhg==" crossorigin="anonymous" referrerpolicy="no-referrer" />
</head>

<body>
    <!-- Header with Hamburger Button -->
    <header>
        <nav class="navbar bg-transparent mx-3 my-2 ">
            <div class="container-fluid">
                <!-- >>>>>>>>> Search Icon -->
                <div class="search_icon_header">
                    <a href="#" class="main_search_icon">
                        <i class="fas fa-search"></i>
                    </a>
                </div>

                <!-- Calling the default search form (if not created a searchform.php in your theme) -->
                <div class="search_form_cont hide ">
                    <?php get_search_form() ?>
                </div>

                <a class="logo" href="<?php echo site_url(); ?>">
                    <img src="<?php echo get_template_directory_uri() ?>/images/notandasLogo.svg" class="img-fluid" />
                </a>
                <button class="navbar-toggler menu-btn ms-auto" type="button" data-bs-toggle="offcanvas" data-bs-target="#offcanvasNavbar" aria-controls="offcanvasNavbar">
                </button>
                <div class="offcanvas offcanvas-end custom-offcanvas" tabindex="-1" id="offcanvasNavbar" aria-labelledby="offcanvasNavbarLabel">
                    <div class="menu-panel">
                        <div class="offcanvas-header border-0 position-absolute top-0 end-0 m-lg-3 m-1">
                            <button type="button" class="btn-close ms-auto" data-bs-dismiss="offcanvas" aria-label="Close"></button>
                        </div>
                        <div class="offcanvas-body p-lg-0 px-lg-5">

                            <!-- Using wp_nav_menu function to call the menu from the wp dashboard -->
                            <?php
                            wp_nav_menu([
                                'theme_location' => 'primary',
                                'depth' => 2,
                                'container' => false,
                                'menu_class' => 'navbar-nav menu-list main_menu',
                            ]);
                            ?>
                        </div>

                        <div class="flex-grow-1"></div>
                    </div>
                </div>
            </div>
        </nav>
    </header>
</body>

</html>

πŸ‘‰ Example Usecase of a footer.php File

<!-- Footer -->
<footer class="pt-5 footer">
    <div class="container">
        <div class="row">

            <!-- >>>>>>>>>>>>>>>>>>>>>> Col 1 -->
            <div class="col-lg-3 col-md-6 col-12 footerCol pe-lg-5">
                <div class="footerLogo">
                    <a href="<?php echo site_url(); ?>"><img src="<?php echo get_field('footer_logo', 'option'); ?>" alt="" class="img-fluid"></a>
                </div>
                <div class="hr-footer"></div>
                <div class="footerDetails">
                    <div class="d-flex justify-content-between align-items-center">
                        <h6 class="mb-3"><?php echo get_field('contact_us_txt', 'option'); ?></h6>
                        <img src="<?php echo get_field('contact_us_icon', 'option'); ?>" class="img-fluid">
                    </div>
                    <p><?php echo get_field('contact_us_addr', 'option'); ?></p>
                    <a href="tel:<?php echo get_field('contact_us_num', 'option'); ?>">
                        <?php echo get_field('contact_us_num', 'option'); ?>
                    </a>
                    <a href="mailto:<?php echo get_field('contact_us_email', 'option'); ?>">
                        <p><?php echo get_field('contact_us_email', 'option'); ?></p>
                    </a>
                </div>
                <div class="mt-3">
                    <ul class="d-flex socialIcons">

                        <?php
                        $contact_us_social_icons = get_field('contact_us_social_icons', 'option');
                        foreach ($contact_us_social_icons as $contact_us_social_icon) {
                        ?>
                            <li><a href="<?php echo $contact_us_social_icon['cont_us_link'] ?>"><img src="<?php echo $contact_us_social_icon['cont_us_icon'] ?>"></a></li>
                        <?php
                        }
                        ?>

                    </ul>

                </div>
            </div>

            <!-- >>>>>>>>>>>>>>>>>>>>>> Col 2 -->
            <div class="col-lg-4 col-md-6 col-12 footerCol ps-lg-5">

                <?php
                $quick_links = get_field('quick_links', 'option');

                foreach ($quick_links as $quick_link) {
                ?>
                    <h5 class="mb-4"><a href="<?php echo $quick_link['ql_links'] ?>"><?php echo $quick_link['ql_text'] ?></a></h5>
                <?php
                }
                ?>

            </div>


            <!-- >>>>>>>>>>>>>>>>>>>>>> Col 3 -->
            <div class="col-lg-2 col-md-6 col-12 footerCol pe-lg-5">
                <h5 class="mb-3"><?php echo get_field('crafted_elegance_title_text', 'option'); ?></h5>
                <ul>
                    <?php
                    $crafted_elegance_reps = get_field('crafted_elegance_rep', 'option');
                    foreach ($crafted_elegance_reps as $crafted_elegance_rep) {
                    ?>
                        <li><a href="<?php echo $crafted_elegance_rep['crafted_elegance_link'] ?>"><?php echo $crafted_elegance_rep['crafted_elegance_txt'] ?></a></li>
                    <?php
                    }
                    ?>
                </ul>
            </div>

            <!-- >>>>>>>>>>>>>>>>>>>>>> Col 4 -->
            <div class="col-lg-3 col-md-6 col-12 footerCol ps-lg-5">
                <h4 class="mb-3"><?php echo get_field('lets_connect_txt', 'option') . "<br>" ?></h4>

                <div class="contactForm">
                    <?php echo do_shortcode('[contact-form-7 id="444f4e7" title="Contact form Footer"]') ?>
                </div>

            </div>
        </div>

        <div class="line mt-lg-5"></div>

        <div class="d-flex flex-column flex-md-row justify-content-between align-items-center text-center text-md-start py-1 copyright">
            <p>Β© <script>
                    document.write(new Date().getFullYear())
                </script> Notandas Realty. All Rights Reserved.</p>
            <a href="https://kwebmaker.com/" target="_blank">Kwebmaker</a>
        </div>
    </div>
</footer>

<!-- List all the Scripting Files here at the Bottom of the Document -->
<script src="<?php echo get_template_directory_uri() ?>/js/jquery-3.7.1.min.js"></script>
<script src="<?php echo get_template_directory_uri() ?>/js/bootstrap.bundle.min.js"></script>
<script src="<?php echo get_template_directory_uri() ?>/js/swiper-bundle.min.js"></script>
<script src="https://unpkg.com/aos@2.3.1/dist/aos.js"></script>
<script src="<?php echo get_template_directory_uri() ?>/js/script.js"></script>
<script src="<?php echo get_template_directory_uri() ?>/js/additional_script.js"></script>

Inside the index.php You can make it blank

<?php
    // >>>>>>>>>>> Code Block Goes Here
?>

Using get_template_directory_uri to get theme URL:

It will return this URL:

/your-site/wp-content/themes/my-custom-theme

We can use this function to get the theme URL to use it in some places eg:

<link rel="stylesheet" href="<?php echo get_template_directory_uri(); ?>/css/bootstrap.min.css">
<link rel="stylesheet" href="<?php echo get_template_directory_uri(); ?>/css/swiper-bundle.min.css">
<link href="https://unpkg.com/aos@2.3.1/dist/aos.css" rel="stylesheet">
<link rel="stylesheet" href="<?php echo get_template_directory_uri(); ?>/css/style.css">
<link rel="stylesheet" href="<?php echo get_template_directory_uri(); ?>/css/animation.css">
<link rel="stylesheet" href="<?php echo get_template_directory_uri(); ?>/css/innerpage.css">

Global Site Setting for Header and Footer

βš™οΈŽ Enable Option Pages

We can directly create option pages from acf backend (PRO Only feature)

Then after create fields and sub-fields for it in the acf and embed data

βœ… Fetch the Data

Then after fetch the data inside your header.php OR footer.php file using:
get_field('footer_logo', 'option'); the_field('footer_logo', 'option');

with an extra argument of 'option' while fetching

πŸ“ Fetching ACF Fields data in the backend:

πŸ“ Fetching Basic Fields

❌ We can use this method to fetch the basic fields eg: (text, textarea, img, etc)

<h2><?php echo get_field('what_we_stand_sec_title') ?></h2>

βœ”οΈ But using this if(): statement method is a good practice (should use it.)

<?php if(get_field('who_we_are_sec_img')): ?>
    <img src="<?php echo the_field('who_we_are_sec_img') ?>" alt="" class="img-fluid">
<?php endif; ?>

πŸ“ Fetching Repeater Field

❌ We can use this (foreach) method But its not a Good Practice:

<?php
$crafted_elegance_reps = get_field('crafted_elegance_rep', 'option');
foreach ($crafted_elegance_reps as $crafted_elegance_rep) {
?>
    <li><a href="<?php echo $crafted_elegance_rep['crafted_elegance_link'] ?>"><?php echo $crafted_elegance_rep['crafted_elegance_txt'] ?></a></li>
<?php
}
?>

βœ”οΈ Using while loop method to fetch a slider's data:

<?php
if(have_rows('what_we_stand_slider')):
    while(have_rows('what_we_stand_slider')): the_row();
?>

    <!-- Slider ~ -->
    <div class="swiper-slide">
        <div class="image-wrapper">
            <?php if(get_sub_field('slider_img')): ?>
                <img src="<?php echo the_sub_field('slider_img') ?>" alt="">
            <?php endif; ?>
        </div>
        <div class="caption">
            <div class="headText col-lg-4 col-md-8 text-center mx-auto mb-4">
                <?php if(get_sub_field('slider_title')): ?>
                    <h5><?php echo the_sub_field('slider_title') ?></h5>
                <?php endif; ?>
                <?php if(get_sub_field('slider_desc')): ?>
                    <p><?php echo the_sub_field('slider_desc') ?></p>
                <?php endif; ?>
            </div>
        </div>
    </div>

<?php
    endwhile;
else:
?>

<h4>No Silder Found!</h4>

<?php
endif;
?>

πŸ“ Fetch Posts (Default and Custom) in a Listing page (eg: Blog Listing)

Fetch all Blog Posts using built-in Wordpress Query:

This will work as a loop for testing pupose you can use it inside an ul > li Then paste it in some container

<?php
// if pagination needed
// $paged = get_query_var('paged') ? get_query_var('paged') : 1; // can be only applicable for built in wp funcs | else need to register for ourselve one
$blog_posts_arr = [
    'post_type' => 'post', // just the post type name change if it is a custom post
    // eg: 'post_type' => 'project',
    'posts_per_page' => 6, // -1 for Infinity
    'order_by' => 'date',
    'order' =>  'ASC', // OR DESC
    // enable if pagination needed
    // 'paged' => $paged, // eg: 1 can use no.s for debugging
    // 'category__in' => [10], // can be fetched as per the post ID, need to pass an array
];

// Enable if filter needed as per the categories
// if ($_GET['select-category']) {
//     $blog_posts_arr['category__in'] = [$_GET['select-category']];
// }

$blog_fetch_query = new WP_Query($blog_posts_arr);

if ($blog_fetch_query->have_posts()):
    while ($blog_fetch_query->have_posts()): $blog_fetch_query->the_post();
?>
        <div class="col-lg-4 col-md-6 mb-4">
            <div class="blogImg">

                <!-- Featured img -->
                <img src="<?php echo the_post_thumbnail_url() ?>" alt="" class="img-fluid">

                <!-- Category -->
                <span class="badge-top-right">
                    <?php
                    $categories = get_the_category();
                    if (!empty($categories)) {
                        foreach ($categories as $cat) {
                            echo '<span>' . esc_html($cat->name) . '</span> ';
                        }
                    }
                    ?>
                </span>
            </div>
            <div class="blogContent">
                <!-- Post Date -->
                <h6><?php echo get_the_date('F j, Y'); ?></h6>
                <!-- Post Link -->
                <a href="<?php echo get_permalink() ?>">
                    <!-- Post Title -->
                    <h4><?php echo the_title() ?></h4>
                </a>
                <!-- Post Excerpt -->
                <p><?php echo the_excerpt() ?></p>
            </div>
        </div>

    <?php
    endwhile;
    wp_reset_postdata(); // very important to reset!
else:
    ?>

<?php
endif;
?>

Add Pagination in the Posts

Don't put it in the same container. Create a new below it then paste the code:

<?php if ($blog_fetch_query->max_num_pages > 1):  ?>
    <div class="pagination">
        <?php
        echo paginate_links([
            'total' => $blog_fetch_query->max_num_pages,
            'current' => $paged,
            'prev_text' => '<i class="fa-solid fa-chevron-left"></i>',
            'next_text' => '<i class="fa-solid fa-chevron-right"></i>',
            'type' => 'list'
        ])
        ?>
    </div>
<?php endif; ?>

Add Filters as per the Category (if needed):

<form method="GET">
    <select name="select-category" class="form-select" id="sortSelect" onchange="this.form.submit()">
        <option value="">Filter</option>
        <?php
        $categories_to_filt = get_categories();
        foreach ($categories_to_filt as $category):
        ?>
            <option
                value="<?php echo $category->term_id ?>"
                <?php
                if ($_GET['select-category'] == $category->term_id) echo 'selected="selected"' ?>>
                <?php echo $category->name ?></option>
        <?php
        endforeach;
        ?>
    </select>
</form>

Fetch Posts (Default and Custom) in its Detail page

βœ… Create the Single Template File in the root directory

WordPress uses the single-{post_type}.php file to render single posts of a custom post type.

Example:

If your custom post type is project, create:

/wp-content/themes/your-theme/single-project.php

If it's for the default blog posts:

/wp-content/themes/your-theme/single.php

βœ… Basic Template Structure

Inside single-book.php or single.php (based on your post type):

<?php
if (have_posts()):
    while (have_posts()): the_post();
?>
        <!-- innerBanner -->
        <section class="homeBanner">
            <div class="container-fluid p-0">
                <div class="innerSlide">
                    <img src="<?php echo get_template_directory_uri() ?>/images/blog-detail.jpg" class="img-fluid w-100" />
                </div>
            </div>
        </section>

        <!-- Anchorlink Section -->
        <section class="linkSection d-none d-lg-block">
            <div class="container">
                <div class="d-flex justify-content-between align-items-center links">
                    <ul class="d-flex align-items-center mb-0 ps-0">
                        <li><a href="#">Blogs</a></li>
                    </ul>
                    <div class="d-flex">
                        <a href="contact.html" class="cta">Reach out</a>
                    </div>
                </div>
            </div>
        </section>

        <!-- Blog content -->
        <section class="pt-4">
            <div class="container">
                <div class="row">
                    <div class="d-flex justify-content-between align-items-center mb-3">
                        <div class="blogContent">
                            <!-- <h6 class="mb-0">October 5, 2023</h6> -->
                            <h6 class="mb-0"><?php echo get_the_date('F j, Y') ?></h6>
                        </div>
                        <div class="share">
                            <button>Share</button>
                        </div>
                    </div>
                </div>

                <div class="row col-lg-12 mb-5">
                    <div class="blogContent">

                        <h3><?php echo the_title() ?></h3>

                        <?php echo the_content() ?>

                    </div>

                    <div class="blogContent">
                        <?php if (get_field('blockquote_text')): ?>
                            <blockquote><?php echo the_field('blockquote_text')  ?></blockquote>
                        <?php endif; ?>

                        <?php if (get_field('below_bq_image')): ?>
                            <div class="imgCaption">
                                <img src="<?php echo the_field('below_bq_image')  ?>" class="img-fluid w-100 mt-3" alt="">
                            </div>
                        <?php endif; ?>
                    </div>
                </div>
            </div>
        </section>

    <?php
    endwhile;
    wp_reset_postdata();
else:
?>
    <h4>No Post Found!</h4>

<?php
endif;
?>

Make sure the ACF fields (author_name, publisher, etc.) exist and are assigned to the correct post type.

βœ… Ensure Permalink Settings Are Correct

If you’ve just registered a custom post type, go to:

Dashboard β†’ Settings β†’ Permalinks β†’ Save Changes

(to flush rewrite rules)

Add Taxonomy Pages:

βœ… Understand What WordPress Loads Automatically

WordPress follows a template hierarchy to decide which file to load:

In WordPress, taxonomy archive pages are automatically created when you register a taxonomy. These archive pages list all posts (or custom post types) assigned to a specific taxonomy term.

For default category taxonomy:

For custom taxonomy (e.g., genre):

Add the files at the root of your theme folder (Not in sub-directories as mentioned earlier multiple times)

βœ… Ensure You Registered the Taxonomy Correctly

When registering the custom taxonomy in functions.php, be sure it’s public and has_archive:

or you can manually add it from wp backend from acf

βœ… Permalinks Flush (Important)

After creating your custom taxonomy and templates:

πŸ” Built-in WP Search Functionality

βœ… Create the Search Form

You can either use the default form via get_search_form() or create your own custom form.

Option A: Default Form

Place this in your header.php, sidebar.php, or anywhere you want:

<?php get_search_form(); ?>

As for example we've used it in our header.php

Option B: Custom Form

Create a file: searchform.php in your theme root:

<form role="search" method="get" class="search-form" action="<?php echo esc_url(home_url('/')); ?>">
    <input type="search" class="search-field" placeholder="Search…" value="<?php echo get_search_query(); ?>" name="s" />
    <button type="submit" class="search-submit">Search</button>
</form>

βœ… Important: The name attribute must be s β€” that's how WordPress recognizes search terms.

βœ… Create search.php Template

This is where the results appear. Create a search.php file in your theme folder:

<?php get_header() ?>

<!-- innerBanner -->
<section class="homeBanner">
    <div class="container-fluid p-0">
        <div class="innerSlide">
            <img src="<?php echo get_template_directory_uri() ?>/images/stand-slider.jpg" class="img-fluid w-100" />
        </div>
    </div>
</section>

<h1 class="search_title">Search Results for: <?php echo get_search_query() ?></h1>

<?php
if (have_posts()):
    while (have_posts()): the_post();
?>

        <div class="cont">
            <div class="left">
                <a href="<?php echo get_permalink() ?>">
                    <img src="<?php echo the_post_thumbnail_url() ?>" alt="">
                </a>
            </div>
            <div class="right">
                <a href="<?php echo get_permalink() ?>">
                    <h3><?php echo the_title() ?></h3>
                    <p class="cont_desc"><?php echo the_excerpt() ?></p>
                    <p class="cont_date"><?php echo get_the_date() ?></p>
                </a>
            </div>
        </div>

    <?php
    endwhile;
else:
?>

    <h4>No Data Found!</h4>

<?php
endif;
?>

<?php get_footer() ?>

βœ… How Search Works

When a user submits the form, WordPress redirects to:

https://yoursite.com/?s=your+search+term

And loads search.php to show results.

Modify Search Form

You can modify the default form via a filter in your functions.php file:

<?php
function custom_search_placeholder($form) {
    $form = str_replace(
        'placeholder="Search …"',
        'placeholder="Type your query..."',
        $form
    );

    return $form;
}
add_filter('get_search_form', 'custom_search_placeholder');
?>

πŸ–‹οΈ All the Important Functions and Queries in one Table

Function / Syntax Context Meaning / Description
if() / else / endif Conditional Used to conditionally render code blocks depending on logic (like checking if fields exist).
foreach() Loop Structure Loops through arrays (e.g., ACF repeater fields or categories).
while() Loop Structure Loops through arrays and objects (e.g., Mainly used for post fetch).
str_replace PHP Function Replaces all occurrences of the search string with the replacement string. (PHP native)
get_field('field_name') ACF – Fetch Fetches a custom field value created via ACF.
the_field('field_name') ACF – Output Echoes the ACF field value directly (instead of returning it).
get_field('field_name', 'option') ACF – Fetch Fetches a custom field value created via ACF. The second param 'option' means it's coming from the global options page.
the_field('field_name', 'option') ACF – Output Echoes the ACF field value directly (instead of returning it).
get_sub_field('sub_field_name') ACF – Repeater Fetches a sub-field inside the current row of a repeater field.
the_sub_field('sub_field_name') ACF – Repeater Echoes the sub-field's value from the current repeater row.
get_option() Site Settings (not in your code but useful) Fetches a value from the WordPress options table (commonly used in plugin/dev settings).
have_rows('repeater_name') ACF – Repeater Checks if the repeater field has any rows of data.
the_row() ACF – Repeater Iterates over each row in a repeater field (must be used inside a loop).
get_template_directory_uri() Theme URL Returns the absolute URL path of the theme folder (used for linking CSS, JS, and images).
site_url() Site Root Returns the root/base URL of the WordPress site.
home_url Custom WP Function Retrieves the URL for the current site’s home page.
add_filter Custom WP Function Adds a custom function to a specific filter hook in WordPress.
do_shortcode('[shortcode]') Shortcode Render Processes and renders WordPress shortcodes from inside PHP (e.g., Contact Form 7).
WP_Query() Custom Loop A powerful class for querying posts, pages, or custom post types with advanced filters.
have_posts() / the_post() Post Loop Standard loop: have_posts() checks for posts; the_post() sets up the post data. (use them with while loop).
the_title() Post Title Echoes the current post title in a WordPress loop. (can be used for listing page as well in detail page)
get_the_date('format') Post Meta Returns the date a post was published, in the specified format. You can make it empty get_the_date() Or can use formate eg: get_the_date('F j, Y') (can be used for listing page as well in detail page)
get_the_content() / the_content() Post Content Returns / Echoes the content of the current post. (can be used for listing page as well in detail page)
get_permalink() Post URL Returns the permalink (URL) of the current post. (can be used for listing page as well in detail page)
the_excerpt() Post Summary Echoes a short summary or trimmed version of the post content. (can be used for listing page as well in detail page)
get_post_thumbnail() Post Featured Image Directly Return the the post's featured image (if available) in the image tag embedded.
get_post_thumbnail_url()
or the_post_thumbnail_url()
Post Featured Image Returns/Echoes the URL of the post's featured image (if available).
get_the_category() Taxonomy Returns the categories the current post belongs to (as an array of category objects).
get_categories() Taxonomy Fetches all categories (terms under 'category' taxonomy).
get_query_var('') For Query Parameter from the URL only works for predefined or registered query vars in WordPress. Custom URL params eg: ?select-category=xyz will not work unless we explicitly register them.
get_query_var('paged') Pagination Gets the current page number for pagination. Commonly used with WP_Query.
category__in WP_Query Param Filters posts in a query to include only those in the specified categories (array of IDs).
post_type WP_Query Param Specifies the type of post to fetch (e.g., 'post', 'project').
posts_per_page WP_Query Param Limits how many posts are returned in the query.
order & orderby WP_Query Param Controls the sort order and field (e.g., date, title).
'paged' WP_Query Param Specifies the current pagination page (for paginated lists).
wp_reset_postdata() Loop Reset Resets global post data after using a custom WP_Query.
paginate_links() Pagination UI Generates pagination links for custom queries (array-based config).
get_term_link() Taxonomy Returns the archive link of a given taxonomy term (not used directly in your code, but relevant for taxonomy pages).
$_GET Filtering Used to retrieve query string values from the URL (e.g., ?select-category=4).
esc_html() / esc_url() Sanitization Sanitizes and escapes text/URL for safe HTML output (used when echoing dynamic content).
get_search_form() Custom WP Function for Search Form Returns the Default WordPress Search Form
get_search_query() Custom WP Function for Search Query Returns the search query string entered by the user.
<script>document.write(new Date().getFullYear())</script> JavaScript Client-side JS that dynamically shows the current year (used for copyright).

πŸ“ You can refer this πŸ‘‰ repository for reference of a Custom Theme in WordPress