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
- Detection Methods
- Implementation Options
- Recommended Approach
- Complete Code Examples
- Advanced Techniques
Detection Methods
There are multiple ways to detect when a search is for shop_product items:
Method 1: Check the Form ID (Recommended)
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(); ?>
Recommended Approach
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:
- Explicit: Form ID clearly identifies the search type
- Reliable: Not dependent on query parameters that could be missing
- Maintainable: Easy to add more specialized searches later
- Flexible: Template parts keep code organized
- 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' => __( '« Previous', 'textdomain' ),
'next_text' => __( 'Next »', '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' => __( '« Previous', 'textdomain' ),
'next_text' => __( 'Next »', '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 |
Recommended Implementation for Your Case
// 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.