WooCommerce Realtime Search API Endpoint for Products, Bricks Builder Search Element

This PHP code adds a custom REST API endpoint (custom/v1/products) to a WooCommerce site, allowing for real-time product searches via AJAX or external requests. When a user sends a GET request with a search query (?search=product-name), the function custom_products_search retrieves up to 10 published WooCommerce products that match the search term. The response includes each product’s ID, title, permalink, price, and featured image (or a default image if none exists). The endpoint is public (permission_callback allows unrestricted access), making it useful for integrating real-time search into a WooCommerce store without requiring user authentication

php:

/* === SEACH PAGE REALTIME SEARCH custom endpoint for woo === */
add_action('rest_api_init', function () {
    register_rest_route('custom/v1', '/products', array(
        'methods'             => 'GET',
        'callback'            => 'custom_products_search',
        'permission_callback' => '__return_true', // Allow public access; adjust if needed.
    ));
});

/**
 * Callback for the custom product search endpoint.
 *
 * Searches for published products matching the "search" query parameter.
 *
 * @param WP_REST_Request $request The current request.
 * @return WP_REST_Response|array The search results.
 */
function custom_products_search(WP_REST_Request $request) {
    // Retrieve and sanitize the search query
    $search_query = sanitize_text_field($request->get_param('search'));

    // Define query arguments for WP_Query
    $args = array(
        'post_type'      => 'product',
        'post_status'    => 'publish',
        's'              => $search_query,
        'posts_per_page' => 10,
    );

    $query   = new WP_Query($args);
    $results = [];

    if ($query->have_posts()) {
        while ($query->have_posts()) {
            $query->the_post();
            $post_id = get_the_ID();

            // Get the product price from WooCommerce meta field
            $price = get_post_meta($post_id, '_price', true);

            // Get the featured image URL (thumbnail size)
            $thumbnail = get_the_post_thumbnail_url($post_id, 'thumbnail');
            if (!$thumbnail) {
                $thumbnail = 'https://yourwebsiteurl.localhost/default-image-if-empty.jpg';
            }

            // Build each product’s result
            $results[] = array(
                'id'        => $post_id,
                'title'     => get_the_title(),
                'link'      => get_permalink(),
                'thumbnail' => $thumbnail,
                'price'     => $price,
            );
        }
        wp_reset_postdata();
    }

    return rest_ensure_response($results);
}


js:

	
/* === SEACH PAGE REALTIME SEARCH === */
	
const MIN_SEARCH_CHARACTERS = 2; // Minimum characters required to trigger a search
const MAX_SEARCH_RESULTS = 6;   // Maximum number of search results to display

document.addEventListener('DOMContentLoaded', function () {
    // Get the search input element and search form element
    const searchInput = document.querySelector('.search-page-input .bricks-search-form input');
    const searchForm = document.querySelector('.search-page-input .bricks-search-form');

    // Check if the necessary DOM elements exist; if not, do not run the rest of the code
    if (!searchInput || !searchForm) {
        return;
    }

    // Create a new ul element to display the product search results and add a custom class for styling
    const resultList = document.createElement('ul');
    resultList.classList.add('custom-search-results');
    resultList.style.marginTop = '10px';
    resultList.style.listStyle = 'none';
    resultList.style.padding = '0';
    searchForm.insertAdjacentElement('afterend', resultList);

    // Function to fetch product search results from the custom WooCommerce REST API endpoint
    async function fetchSearchResults(query) {
        const response = await fetch(`/wp-json/custom/v1/products?search=${query}`);
        if (response.ok) {
            const data = await response.json();
            return data;
        }
        return [];
    }

    // Function to display product search results
    function displayResults(results) {
        // Clear the current result list
        resultList.innerHTML = '';

        // Limit results to the maximum number specified in settings
        results.slice(0, MAX_SEARCH_RESULTS).forEach(product => {
            // Create a list item for each product
            const li = document.createElement('li');
            li.style.display = 'flex';
            li.style.alignItems = 'center';
            li.style.justifyContent = 'space-between';
            li.style.marginBottom = '10px';
            li.style.border = '1px solid #ddd';
            li.style.padding = '5px';
            li.style.borderRadius = '4px';

            // Create a container for the thumbnail and title (left side)
            const leftDiv = document.createElement('div');
            leftDiv.style.display = 'flex';
            leftDiv.style.alignItems = 'center';

            // Create and set up the thumbnail image
            const img = document.createElement('img');
            img.src = product.thumbnail ? product.thumbnail : 'https://dev.baggy.geopard-digital.com/wp-content/uploads/2025/02/image-30124.webp';
            img.alt = product.title;
            img.style.width = '50px';
            img.style.height = '50px';
            img.style.objectFit = 'cover';
            leftDiv.appendChild(img);

            // Create the title span and add a little margin to separate it from the image
            const titleSpan = document.createElement('span');
            titleSpan.textContent = product.title;
            titleSpan.style.marginLeft = '10px';
            leftDiv.appendChild(titleSpan);

            li.appendChild(leftDiv);

            // Create the price element (right side)
            const priceSpan = document.createElement('span');
            priceSpan.textContent = product.price ? product.price + ' €' : '';
            li.appendChild(priceSpan);

            // Wrap the entire list item in an anchor tag for linking to the product page
            const link = document.createElement('a');
            link.href = product.link;
            link.style.textDecoration = 'none';
            link.style.color = 'inherit';
            link.appendChild(li);

            resultList.appendChild(link);
        });
    }

    // Add an event listener to the search input for real-time product search
    searchInput.addEventListener('input', async function () {
        const query = searchInput.value.trim();

        // Perform the product search only if the query has at least the minimum number of characters specified in settings
        if (query.length >= MIN_SEARCH_CHARACTERS) {
            const results = await fetchSearchResults(query);
            displayResults(results);
        } else {
            // Clear the result list if the query is less than the minimum number of characters required
            resultList.innerHTML = '';
        }
    });
});