WordPress / 6 MIN READ
Search Bar Rest API
Creating a Search Bar with Rest API
From the original Fervor library. Examples may use older package versions.
π Tutorial: Cross-Site Product Search Using the WordPress REST API
Search products on store.example.com from example.com
π§° What You Need
| Requirement | Setup |
|---|---|
| Site A | example.com β where your users will search |
| Site B | store.example.com β where the WooCommerce products live |
| API Access | WordPress REST API is built-in and enabled by default |
| (Optional) | Use CoCart or custom endpoint if you want more product data (price, images, etc.) |
β
Step 1: Test the REST API on store.example.com
Open this in your browser:
https://store.example.com/wp-json/wp/v2/product?s=shirt
You should see JSON data. Each product entry will include:
title.renderedexcerpt.renderedlink
β
Step 2: Add a Search Form on example.com
Paste this HTML anywhere on example.com β in a page, template, or widget:
<form id="product-search-form">
<input type="text" id="search-query" placeholder="Search products from store..." />
<button type="submit">Search</button>
</form>
<div id="product-search-results"></div>
β
Step 3: Add JavaScript to Fetch Results from store.example.com
Paste this in the footer or enqueue it properly in a separate JS file:
<script>
document.getElementById('product-search-form').addEventListener('submit', function (e) {
e.preventDefault();
const query = document.getElementById('search-query').value;
const resultsContainer = document.getElementById('product-search-results');
resultsContainer.innerHTML = 'Searching...';
fetch(`https://store.example.com/wp-json/wp/v2/product?s=${encodeURIComponent(query)}`)
.then(response => response.json())
.then(products => {
if (!products.length) {
resultsContainer.innerHTML = '<p>No products found.</p>';
return;
}
const resultsHTML = products.map(product => `
<div class="product-result">
<h3><a href="${product.link}" target="_blank">${product.title.rendered}</a></h3>
<p>${product.excerpt.rendered}</p>
</div>
`).join('');
resultsContainer.innerHTML = resultsHTML;
})
.catch(error => {
console.error('Search error:', error);
resultsContainer.innerHTML = '<p>There was an error fetching results.</p>';
});
});
</script>
β Step 4: (Optional) Style the Search Results
Add this CSS to your themeβs Additional CSS section:
.product-result {
padding: 10px;
border-bottom: 1px solid #ccc;
margin-bottom: 10px;
}
.product-result h3 {
margin: 0 0 5px;
font-size: 18px;
}
.product-result p {
margin: 0;
color: #555;
}
π Bonus: Add Price, Images, etc.
By default, the WordPress REST API wonβt return product price or image. To get those, either:
-
Use a plugin like CoCart or WP REST API Controller
-
Or write a custom REST API endpoint on
store.example.comthat returns:titleimage URLpricepermalink
Let me know if you want a working custom endpoint example β Iβll build one for you.
β Summary
| Step | Description |
|---|---|
| π REST API | Fetch products from store.example.com |
| π§Ύ Search Form | Add form to example.com |
| βοΈ JavaScript | Make the API request and render results |
| π Results | Link each result back to store.example.com/product/... |
#Bonus Time
Letβs upgrade this search so it also returns product thumbnails and prices from store.example.com, and still displays them on example.com.
Since the default WordPress REST API doesnβt expose price or image, weβll create a custom REST endpoint on store.example.com that returns:
- Product title
- Permalink
- Price
- Product image URL
- Excerpt/description
βοΈ PART 1: Create the Custom REST API Endpoint on store.example.com
Add this to your themeβs functions.php or in a custom plugin on store.example.com:
add_action('rest_api_init', function () {
register_rest_route('custom/v1', '/product-search', [
'methods' => 'GET',
'callback' => 'custom_product_search',
'permission_callback' => '__return_true', // Public access
]);
});
function custom_product_search($request) {
$term = sanitize_text_field($request->get_param('s'));
$query = new WP_Query([
'post_type' => 'product',
'posts_per_page' => 10,
's' => $term,
'post_status' => 'publish',
]);
$results = [];
while ($query->have_posts()) {
$query->the_post();
global $product;
$product_obj = wc_get_product(get_the_ID());
$results[] = [
'title' => get_the_title(),
'excerpt' => get_the_excerpt(),
'link' => get_permalink(),
'price' => $product_obj ? $product_obj->get_price_html() : '',
'image' => get_the_post_thumbnail_url(get_the_ID(), 'medium'),
];
}
wp_reset_postdata();
return $results;
}
β This creates a public endpoint:
https://store.example.com/wp-json/custom/v1/product-search?s=your+query
π» PART 2: Update example.com to Use the New API and Display Price + Image
β HTML (same as before):
<form id="product-search-form">
<input type="text" id="search-query" placeholder="Search products from store..." />
<button type="submit">Search</button>
</form>
<div id="product-search-results"></div>
β JavaScript (enhanced):
<script>
document.getElementById('product-search-form').addEventListener('submit', function (e) {
e.preventDefault();
const query = document.getElementById('search-query').value;
const resultsContainer = document.getElementById('product-search-results');
resultsContainer.innerHTML = 'Searching...';
fetch(`https://store.example.com/wp-json/custom/v1/product-search?s=${encodeURIComponent(query)}`)
.then(response => response.json())
.then(products => {
if (!products.length) {
resultsContainer.innerHTML = '<p>No products found.</p>';
return;
}
const resultsHTML = products.map(product => `
<div class="product-result">
<a href="${product.link}" target="_blank">
<img src="${product.image}" alt="${product.title}" style="max-width: 150px; display: block; margin-bottom: 10px;">
<h3>${product.title}</h3>
</a>
<p>${product.excerpt}</p>
<strong>${product.price}</strong>
</div>
`).join('');
resultsContainer.innerHTML = resultsHTML;
})
.catch(error => {
console.error('Search error:', error);
resultsContainer.innerHTML = '<p>There was an error fetching results.</p>';
});
});
</script>
β Optional CSS:
.product-result {
padding: 15px;
border: 1px solid #eee;
margin-bottom: 15px;
border-radius: 5px;
background: #fafafa;
}
.product-result img {
max-width: 100%;
height: auto;
}
.product-result h3 {
margin: 10px 0 5px;
}
.product-result p {
margin: 0 0 10px;
}
π Final Result
- User searches from
example.com - The form queries
store.example.comvia your custom API - Results show image, title, price, excerpt
- Clicking sends the user to the product page on
store.example.com
Letβs take this to the next level by adding category filtering and pagination to your remote product search system.
π Upgraded Tutorial:
Cross-Site Product Search with Category Filter + Pagination
From example.com β Fetching products on store.example.com
βοΈ PART 1: Update the Custom REST API Endpoint (on store.example.com)
In your themeβs functions.php or a custom plugin, replace the previous endpoint code with this enhanced version:
add_action('rest_api_init', function () {
register_rest_route('custom/v1', '/product-search', [
'methods' => 'GET',
'callback' => 'custom_product_search',
'permission_callback' => '__return_true',
]);
});
function custom_product_search($request) {
$term = sanitize_text_field($request->get_param('s'));
$category = sanitize_text_field($request->get_param('category'));
$paged = absint($request->get_param('page')) ?: 1;
$args = [
'post_type' => 'product',
'posts_per_page' => 5,
'paged' => $paged,
's' => $term,
'post_status' => 'publish',
];
if (!empty($category)) {
$args['tax_query'] = [[
'taxonomy' => 'product_cat',
'field' => 'slug',
'terms' => $category,
]];
}
$query = new WP_Query($args);
$results = [];
while ($query->have_posts()) {
$query->the_post();
$product = wc_get_product(get_the_ID());
$results[] = [
'title' => get_the_title(),
'excerpt' => get_the_excerpt(),
'link' => get_permalink(),
'price' => $product ? $product->get_price_html() : '',
'image' => get_the_post_thumbnail_url(get_the_ID(), 'medium'),
];
}
wp_reset_postdata();
return [
'products' => $results,
'total_pages' => $query->max_num_pages,
'current_page'=> $paged,
];
}
β This now supports:
?s=term&category=category-slug&page=2
π PART 2: Update the Search Form on example.com
Add category selection and pagination:
<form id="product-search-form">
<input type="text" id="search-query" placeholder="Search products..." />
<select id="category-filter">
<option value="">All Categories</option>
<option value="shirts">Shirts</option>
<option value="hats">Hats</option>
<option value="accessories">Accessories</option>
</select>
<button type="submit">Search</button>
</form>
<div id="product-search-results"></div>
<div id="pagination-controls"></div>
π§ PART 3: JavaScript β Add Filtering & Pagination Support
<script>
let currentPage = 1;
function fetchProducts(page = 1) {
const query = document.getElementById('search-query').value;
const category = document.getElementById('category-filter').value;
const resultsContainer = document.getElementById('product-search-results');
const paginationContainer = document.getElementById('pagination-controls');
resultsContainer.innerHTML = 'Searching...';
paginationContainer.innerHTML = '';
const apiUrl = `https://store.example.com/wp-json/custom/v1/product-search?s=${encodeURIComponent(query)}&category=${encodeURIComponent(category)}&page=${page}`;
fetch(apiUrl)
.then(res => res.json())
.then(data => {
const products = data.products;
const totalPages = data.total_pages;
if (!products.length) {
resultsContainer.innerHTML = '<p>No products found.</p>';
return;
}
const productHTML = products.map(product => `
<div class="product-result">
<a href="${product.link}" target="_blank">
<img src="${product.image}" alt="${product.title}" style="max-width: 150px;">
<h3>${product.title}</h3>
</a>
<p>${product.excerpt}</p>
<strong>${product.price}</strong>
</div>
`).join('');
resultsContainer.innerHTML = productHTML;
// Pagination
let paginationHTML = '';
for (let i = 1; i <= totalPages; i++) {
paginationHTML += `<button class="page-btn" data-page="${i}" ${i === page ? 'disabled' : ''}>${i}</button>`;
}
paginationContainer.innerHTML = paginationHTML;
document.querySelectorAll('.page-btn').forEach(btn => {
btn.addEventListener('click', () => {
currentPage = parseInt(btn.dataset.page);
fetchProducts(currentPage);
});
});
})
.catch(err => {
console.error(err);
resultsContainer.innerHTML = '<p>Error fetching results.</p>';
});
}
document.getElementById('product-search-form').addEventListener('submit', function (e) {
e.preventDefault();
currentPage = 1;
fetchProducts(currentPage);
});
</script>
β Optional CSS for Pagination
#pagination-controls {
margin-top: 20px;
}
#pagination-controls button {
margin: 0 4px;
padding: 5px 10px;
cursor: pointer;
}
#pagination-controls button[disabled] {
background: #ddd;
cursor: default;
}
π Final Features Enabled
β
Real-time WooCommerce product search from store.example.com
β
Links open original product pages
β
Thumbnail + price
β
Category filter
β
Pagination for clean UX