Options for Different Search Templates Based on shop_product Post Type

Overview

You want to display a different search template/layout in your native WordPress search.php page when the search is specifically for shop_product post types. Since your SearchWP engine for products only includes the shop_product post_type, you have several reliable methods to detect and handle this scenario.

Table of Contents

  1. Detection Methods
  2. Implementation Options
  3. Recommended Approach
  4. Complete Code Examples
  5. Advanced Techniques

Detection Methods

There are multiple ways to detect when a search is for shop_product items:

Best when: You have a dedicated search form for products

The most reliable method is to check which SearchWP form initiated the search using the swp_form[form_id] parameter.

// In search.php
$form_id = isset( $_GET['swp_form']['form_id'] ) ? absint( $_GET['swp_form']['form_id'] ) : 0;

// Assuming form ID 2 is your product search form
if ( $form_id === 2 ) {
    // This is a product search
    get_template_part( 'template-parts/search', 'products' );
} else {
    // Regular search
    get_template_part( 'template-parts/search', 'default' );
}

Advantages:

  • Most explicit and reliable
  • Easy to maintain and understand
  • Works regardless of query parameters
  • Can handle multiple specialized forms

Reference: SWP_FORM_PARAMETER_USAGE.md lines 449-469


Method 2: Check Post Type Limiter Parameter

Best when: Users can filter search by post type

Check if the swp_post_type_limiter parameter is set to shop_product.

// In search.php
$post_type_limiter = isset( $_GET['swp_post_type_limiter'] )
    ? sanitize_text_field( $_GET['swp_post_type_limiter'] )
    : '';

if ( $post_type_limiter === 'shop_product' ) {
    // Product search with filter active
    get_template_part( 'template-parts/search', 'products' );
} else {
    get_template_part( 'template-parts/search', 'default' );
}

Advantages:

  • Works with advanced search filters
  • Respects user’s explicit filter choice
  • Simple to implement

Limitations:

  • Only works if the advanced search filter is being used
  • Won’t catch product searches without the filter parameter

Reference: includes/Forms/Frontend.php:872-933


Method 3: Inspect the Search Results

Best when: You want to dynamically detect based on actual results

Check the post type of the returned results after the search executes.

// In search.php
global $wp_query;

$is_product_search = false;

if ( have_posts() ) {
    // Peek at the first result to determine post type
    $first_post = $wp_query->posts[0] ?? null;

    if ( $first_post && get_post_type( $first_post ) === 'shop_product' ) {
        $is_product_search = true;
    }
}

if ( $is_product_search ) {
    get_template_part( 'template-parts/search', 'products' );
} else {
    get_template_part( 'template-parts/search', 'default' );
}

Advantages:

  • Works based on actual returned data
  • No need to track form IDs or parameters
  • Adapts to mixed results

Limitations:

  • Requires results to exist (doesn’t work for empty results)
  • Assumes all results are the same post type
  • Less predictable if engine includes multiple post types

Method 4: Check the SearchWP Engine Configuration

Best when: You have a dedicated engine for products

Retrieve the form configuration and check which engine is being used, then inspect that engine’s sources.

// In search.php
use SearchWP\Forms\Storage as SearchWPFormsStorage;
use SearchWP\Settings;

$form_id = isset( $_GET['swp_form']['form_id'] ) ? absint( $_GET['swp_form']['form_id'] ) : 0;

$is_product_search = false;

if ( $form_id ) {
    $form = SearchWPFormsStorage::get( $form_id );

    if ( $form ) {
        $engine_name = ! empty( $form['engine'] ) ? $form['engine'] : 'default';
        $engine_settings = Settings::get_engine_settings( $engine_name );

        if ( $engine_settings ) {
            $sources = isset( $engine_settings['sources'] ) ? $engine_settings['sources'] : [];

            // Check if the only source is shop_product
            if ( isset( $sources['post__shop_product'] ) && count( $sources ) === 1 ) {
                $is_product_search = true;
            }
        }
    }
}

if ( $is_product_search ) {
    get_template_part( 'template-parts/search', 'products' );
} else {
    get_template_part( 'template-parts/search', 'default' );
}

Advantages:

  • Very robust - based on actual engine configuration
  • Automatically adapts if engine configuration changes
  • Works even with empty results

Limitations:

  • More code complexity
  • Requires understanding of SearchWP internals
  • Needs access to SearchWP classes

Reference: includes/Forms/Frontend.php:604-631 and includes/Settings.php


Method 5: Use a Custom Query Variable

Best when: You want complete control and explicit signaling

Set a custom query variable in your form’s target URL or via a filter.

// In functions.php - Set the query var
add_filter( 'query_vars', function( $vars ) {
    $vars[] = 'search_type';
    return $vars;
} );

// In your product search form - add to target URL
// URL: /search/?swp_form[form_id]=2&s=coffee&search_type=products

// In search.php - check the query var
$search_type = get_query_var( 'search_type' );

if ( $search_type === 'products' ) {
    get_template_part( 'template-parts/search', 'products' );
} else {
    get_template_part( 'template-parts/search', 'default' );
}

Advantages:

  • Clean and semantic
  • Easy to test and debug
  • Works with any search method

Limitations:

  • Requires modifying form URLs or adding filters
  • Additional setup required

Implementation Options

Once you’ve detected a product search, you have several options for displaying different layouts:

Option A: Separate Template Parts

Structure:

theme/
├── search.php
├── template-parts/
│   ├── search-default.php
│   └── search-products.php

Implementation:

// search.php
<?php get_header(); ?>

<div class="search-results-container">
    <?php
    $form_id = isset( $_GET['swp_form']['form_id'] ) ? absint( $_GET['swp_form']['form_id'] ) : 0;

    if ( $form_id === 2 ) { // Product form ID
        get_template_part( 'template-parts/search', 'products' );
    } else {
        get_template_part( 'template-parts/search', 'default' );
    }
    ?>
</div>

<?php get_footer(); ?>

Option B: Conditional Logic Within search.php

Single file with conditional blocks:

// search.php
<?php
get_header();

$form_id = isset( $_GET['swp_form']['form_id'] ) ? absint( $_GET['swp_form']['form_id'] ) : 0;
$is_product_search = ( $form_id === 2 );
?>

<div class="search-results-container">
    <header class="search-header">
        <h1>
            <?php if ( $is_product_search ) : ?>
                Product Search Results for: <?php echo get_search_query(); ?>
            <?php else : ?>
                Search Results for: <?php echo get_search_query(); ?>
            <?php endif; ?>
        </h1>
    </header>

    <?php if ( have_posts() ) : ?>

        <?php if ( $is_product_search ) : ?>
            <!-- Product Grid Layout -->
            <div class="product-grid">
                <?php while ( have_posts() ) : the_post(); ?>
                    <div class="product-card">
                        <?php if ( has_post_thumbnail() ) : ?>
                            <div class="product-image">
                                <?php the_post_thumbnail( 'medium' ); ?>
                            </div>
                        <?php endif; ?>

                        <h3 class="product-title">
                            <a href="<?php the_permalink(); ?>"><?php the_title(); ?></a>
                        </h3>

                        <?php if ( function_exists( 'wc_get_product' ) ) : ?>
                            <?php $product = wc_get_product( get_the_ID() ); ?>
                            <div class="product-price">
                                <?php echo $product->get_price_html(); ?>
                            </div>
                        <?php endif; ?>

                        <a href="<?php the_permalink(); ?>" class="btn-view-product">
                            View Product
                        </a>
                    </div>
                <?php endwhile; ?>
            </div>

        <?php else : ?>
            <!-- Standard List Layout -->
            <div class="search-list">
                <?php while ( have_posts() ) : the_post(); ?>
                    <article class="search-result">
                        <h2 class="entry-title">
                            <a href="<?php the_permalink(); ?>"><?php the_title(); ?></a>
                        </h2>

                        <div class="entry-summary">
                            <?php the_excerpt(); ?>
                        </div>

                        <a href="<?php the_permalink(); ?>" class="read-more">
                            Read More
                        </a>
                    </article>
                <?php endwhile; ?>
            </div>
        <?php endif; ?>

        <?php the_posts_pagination(); ?>

    <?php else : ?>
        <p>No results found.</p>
    <?php endif; ?>
</div>

<?php get_footer(); ?>

Option C: Completely Separate Template Files

Use WordPress’s template hierarchy:

Create a custom template that’s loaded based on a query variable or action.

Structure:

theme/
├── search.php (default)
├── search-products.php (custom)

Implementation:

// In functions.php
add_filter( 'template_include', function( $template ) {
    if ( is_search() ) {
        $form_id = isset( $_GET['swp_form']['form_id'] ) ? absint( $_GET['swp_form']['form_id'] ) : 0;

        // If product search form (ID 2)
        if ( $form_id === 2 ) {
            $product_template = locate_template( 'search-products.php' );
            if ( $product_template ) {
                return $product_template;
            }
        }
    }

    return $template;
}, 99 );
// search-products.php
<?php get_header(); ?>

<div class="product-search-results">
    <h1>Product Search: <?php echo get_search_query(); ?></h1>

    <?php if ( have_posts() ) : ?>
        <div class="woocommerce">
            <ul class="products columns-3">
                <?php while ( have_posts() ) : the_post(); ?>
                    <?php wc_get_template_part( 'content', 'product' ); ?>
                <?php endwhile; ?>
            </ul>
        </div>

        <?php the_posts_pagination(); ?>
    <?php else : ?>
        <p>No products found.</p>
    <?php endif; ?>
</div>

<?php get_footer(); ?>

For your specific case (dedicated engine with only shop_product):

Best Solution: Form ID Detection + Separate Template Parts

This approach is clean, maintainable, and explicitly tied to your search forms.

// search.php
<?php
/**
 * Search Results Template
 */

get_header();

// Detect which search form was used
$form_id = isset( $_GET['swp_form']['form_id'] ) ? absint( $_GET['swp_form']['form_id'] ) : 0;

// Form ID 2 = Product Search (adjust to your actual form ID)
// This form uses an engine that only includes shop_product
$is_product_search = ( $form_id === 2 );

?>

<main id="primary" class="site-main">
    <header class="page-header">
        <h1 class="page-title">
            <?php
            if ( $is_product_search ) {
                printf(
                    esc_html__( 'Product Search Results for: %s', 'textdomain' ),
                    '<span>' . get_search_query() . '</span>'
                );
            } else {
                printf(
                    esc_html__( 'Search Results for: %s', 'textdomain' ),
                    '<span>' . get_search_query() . '</span>'
                );
            }
            ?>
        </h1>
    </header>

    <?php
    if ( $is_product_search ) {
        // Load product-specific template
        get_template_part( 'template-parts/search', 'products' );
    } else {
        // Load default search template
        get_template_part( 'template-parts/search', 'default' );
    }
    ?>

</main>

<?php
get_sidebar();
get_footer();

Why this is best:

  1. Explicit: Form ID clearly identifies the search type
  2. Reliable: Not dependent on query parameters that could be missing
  3. Maintainable: Easy to add more specialized searches later
  4. Flexible: Template parts keep code organized
  5. Semantic: The form configuration defines the search behavior

Complete Code Examples

Example 1: Product Search Template (template-parts/search-products.php)

<?php
/**
 * Product Search Results Template Part
 */

if ( ! have_posts() ) : ?>

    <div class="no-results not-found">
        <p><?php esc_html_e( 'No products found matching your search.', 'textdomain' ); ?></p>
        <p><?php esc_html_e( 'Try different keywords or browse our catalog.', 'textdomain' ); ?></p>
    </div>

<?php else : ?>

    <div class="search-results-meta">
        <p class="results-count">
            <?php
            global $wp_query;
            printf(
                esc_html( _n(
                    'Found %s product',
                    'Found %s products',
                    $wp_query->found_posts,
                    'textdomain'
                ) ),
                number_format_i18n( $wp_query->found_posts )
            );
            ?>
        </p>
    </div>

    <div class="product-grid columns-3">
        <?php while ( have_posts() ) : the_post(); ?>

            <article id="post-<?php the_ID(); ?>" <?php post_class( 'product-card' ); ?>>

                <?php if ( has_post_thumbnail() ) : ?>
                    <div class="product-thumbnail">
                        <a href="<?php the_permalink(); ?>">
                            <?php the_post_thumbnail( 'medium' ); ?>
                        </a>
                    </div>
                <?php endif; ?>

                <div class="product-info">
                    <h3 class="product-title">
                        <a href="<?php the_permalink(); ?>">
                            <?php the_title(); ?>
                        </a>
                    </h3>

                    <?php if ( function_exists( 'wc_get_product' ) ) : ?>
                        <?php $product = wc_get_product( get_the_ID() ); ?>

                        <?php if ( $product ) : ?>
                            <div class="product-price">
                                <?php echo $product->get_price_html(); ?>
                            </div>

                            <?php if ( $product->is_in_stock() ) : ?>
                                <span class="stock in-stock">
                                    <?php esc_html_e( 'In Stock', 'textdomain' ); ?>
                                </span>
                            <?php else : ?>
                                <span class="stock out-of-stock">
                                    <?php esc_html_e( 'Out of Stock', 'textdomain' ); ?>
                                </span>
                            <?php endif; ?>
                        <?php endif; ?>
                    <?php endif; ?>

                    <div class="product-excerpt">
                        <?php echo wp_trim_words( get_the_excerpt(), 15 ); ?>
                    </div>

                    <a href="<?php the_permalink(); ?>" class="button view-product">
                        <?php esc_html_e( 'View Product', 'textdomain' ); ?>
                    </a>
                </div>

            </article>

        <?php endwhile; ?>
    </div>

    <?php
    the_posts_pagination( array(
        'mid_size'  => 2,
        'prev_text' => __( '&laquo; Previous', 'textdomain' ),
        'next_text' => __( 'Next &raquo;', 'textdomain' ),
    ) );
    ?>

<?php endif; ?>

Example 2: Default Search Template (template-parts/search-default.php)

<?php
/**
 * Default Search Results Template Part
 */

if ( ! have_posts() ) : ?>

    <div class="no-results not-found">
        <header class="page-header">
            <h2 class="page-title">
                <?php esc_html_e( 'Nothing Found', 'textdomain' ); ?>
            </h2>
        </header>

        <div class="page-content">
            <p><?php esc_html_e( 'Sorry, but nothing matched your search terms. Please try again with different keywords.', 'textdomain' ); ?></p>
            <?php get_search_form(); ?>
        </div>
    </div>

<?php else : ?>

    <div class="search-results-meta">
        <p class="results-count">
            <?php
            global $wp_query;
            printf(
                esc_html( _n(
                    'Found %s result',
                    'Found %s results',
                    $wp_query->found_posts,
                    'textdomain'
                ) ),
                number_format_i18n( $wp_query->found_posts )
            );
            ?>
        </p>
    </div>

    <div class="search-results-list">
        <?php while ( have_posts() ) : the_post(); ?>

            <article id="post-<?php the_ID(); ?>" <?php post_class( 'search-result-item' ); ?>>

                <?php if ( has_post_thumbnail() ) : ?>
                    <div class="result-thumbnail">
                        <a href="<?php the_permalink(); ?>">
                            <?php the_post_thumbnail( 'thumbnail' ); ?>
                        </a>
                    </div>
                <?php endif; ?>

                <div class="result-content">
                    <header class="entry-header">
                        <h2 class="entry-title">
                            <a href="<?php the_permalink(); ?>" rel="bookmark">
                                <?php the_title(); ?>
                            </a>
                        </h2>

                        <div class="entry-meta">
                            <span class="post-type">
                                <?php echo esc_html( get_post_type_object( get_post_type() )->labels->singular_name ); ?>
                            </span>
                            <span class="post-date">
                                <?php echo get_the_date(); ?>
                            </span>
                        </div>
                    </header>

                    <div class="entry-summary">
                        <?php the_excerpt(); ?>
                    </div>

                    <a href="<?php the_permalink(); ?>" class="read-more">
                        <?php esc_html_e( 'Read More', 'textdomain' ); ?>
                    </a>
                </div>

            </article>

        <?php endwhile; ?>
    </div>

    <?php
    the_posts_pagination( array(
        'mid_size'  => 2,
        'prev_text' => __( '&laquo; Previous', 'textdomain' ),
        'next_text' => __( 'Next &raquo;', 'textdomain' ),
    ) );
    ?>

<?php endif; ?>

Example 3: With Conditional CSS

You can also add conditional body classes for easier styling:

// In functions.php
add_filter( 'body_class', function( $classes ) {
    if ( is_search() ) {
        $form_id = isset( $_GET['swp_form']['form_id'] ) ? absint( $_GET['swp_form']['form_id'] ) : 0;

        if ( $form_id === 2 ) {
            $classes[] = 'search-products';
            $classes[] = 'woocommerce'; // If you want WooCommerce styles
        } else {
            $classes[] = 'search-default';
        }
    }

    return $classes;
} );

Then in your CSS:

/* Product search specific styles */
.search-products .product-grid {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 2rem;
}

.search-products .product-card {
    border: 1px solid #ddd;
    border-radius: 8px;
    padding: 1rem;
}

.search-products .product-thumbnail img {
    width: 100%;
    height: auto;
}

/* Default search styles */
.search-default .search-results-list {
    display: flex;
    flex-direction: column;
    gap: 2rem;
}

.search-default .search-result-item {
    display: flex;
    gap: 1.5rem;
}

Advanced Techniques

Technique 1: Hybrid Detection (Fallback Logic)

Combine multiple detection methods for maximum reliability:

// search.php
function is_product_search() {
    // Method 1: Check form ID (most reliable)
    $form_id = isset( $_GET['swp_form']['form_id'] ) ? absint( $_GET['swp_form']['form_id'] ) : 0;
    if ( $form_id === 2 ) {
        return true;
    }

    // Method 2: Check post type limiter parameter
    $post_type_limiter = isset( $_GET['swp_post_type_limiter'] )
        ? sanitize_text_field( $_GET['swp_post_type_limiter'] )
        : '';
    if ( $post_type_limiter === 'shop_product' ) {
        return true;
    }

    // Method 3: Check actual results
    global $wp_query;
    if ( have_posts() ) {
        $first_post = $wp_query->posts[0] ?? null;
        if ( $first_post && get_post_type( $first_post ) === 'shop_product' ) {
            return true;
        }
    }

    return false;
}

// Usage
if ( is_product_search() ) {
    get_template_part( 'template-parts/search', 'products' );
} else {
    get_template_part( 'template-parts/search', 'default' );
}

Technique 2: Dynamic Template Selection

Create a system that automatically selects templates based on configuration:

// In functions.php
function get_search_template_type() {
    $form_id = isset( $_GET['swp_form']['form_id'] ) ? absint( $_GET['swp_form']['form_id'] ) : 0;

    // Map form IDs to template types
    $form_templates = array(
        1 => 'default',  // General search
        2 => 'products', // Product search
        3 => 'posts',    // Blog posts only
        4 => 'media',    // Media library search
    );

    return isset( $form_templates[ $form_id ] )
        ? $form_templates[ $form_id ]
        : 'default';
}

// In search.php
$template_type = get_search_template_type();
get_template_part( 'template-parts/search', $template_type );

Technique 3: Hook-Based Template Switching

Let other parts of your code modify the template:

// In functions.php
function get_search_template_name() {
    $template = 'default';

    $form_id = isset( $_GET['swp_form']['form_id'] ) ? absint( $_GET['swp_form']['form_id'] ) : 0;

    if ( $form_id === 2 ) {
        $template = 'products';
    }

    // Allow other plugins/themes to modify
    return apply_filters( 'theme_search_template', $template, $form_id );
}

// In search.php
$template_name = get_search_template_name();
get_template_part( 'template-parts/search', $template_name );

// Other code can now modify this:
add_filter( 'theme_search_template', function( $template, $form_id ) {
    if ( $form_id === 5 ) {
        return 'custom-archive';
    }
    return $template;
}, 10, 2 );

Summary

Quick Reference Table

Method Best For Reliability Complexity Works with Empty Results
Form ID Detection Dedicated forms ⭐⭐⭐⭐⭐ Low ✅ Yes
Post Type Limiter Advanced filters ⭐⭐⭐⭐ Low ✅ Yes
Inspect Results Dynamic detection ⭐⭐⭐ Low ❌ No
Engine Configuration Auto-adaptation ⭐⭐⭐⭐⭐ High ✅ Yes
Custom Query Var Full control ⭐⭐⭐⭐ Medium ✅ Yes
// search.php
<?php
get_header();

// Simple, reliable detection
$form_id = isset( $_GET['swp_form']['form_id'] ) ? absint( $_GET['swp_form']['form_id'] ) : 0;
$is_product_search = ( $form_id === 2 ); // Replace 2 with your actual product form ID

?>

<main id="primary" class="site-main">
    <header class="page-header">
        <h1 class="page-title">
            <?php
            if ( $is_product_search ) {
                printf( __( 'Product Search: %s', 'textdomain' ), get_search_query() );
            } else {
                printf( __( 'Search Results: %s', 'textdomain' ), get_search_query() );
            }
            ?>
        </h1>
    </header>

    <?php
    if ( $is_product_search ) {
        get_template_part( 'template-parts/search', 'products' );
    } else {
        get_template_part( 'template-parts/search', 'default' );
    }
    ?>
</main>

<?php
get_sidebar();
get_footer();

This approach is:

  • Simple: One clear condition
  • Reliable: Based on explicit form configuration
  • Maintainable: Easy to understand and modify
  • Flexible: Easy to add more specialized templates
  • Performance: No extra database queries needed

The key insight is that since your product engine only contains shop_product, checking the form ID is the most direct and reliable way to determine if you should show the product template.