Magento 2 Experten — Hyvä Theme, Tailwind CSS & SEO aus einer Hand ›

Server-Side Search With searchCriteria Filters

Server-Side Search With searchCriteria Filters

~13 Min. Lesezeit Zuletzt aktualisiert am August 8, 2026

To close out the functional chapters, let's build REAL search – not client-side filtering of an already-loaded array, but a request that lets Magento ITSELF search for matching products.

Why server-side instead of client-side filtering?

A client-side filter (products.filter(p => p.name.includes(query))) would ONLY search WITHIN the already-loaded 12 products of the current page – a product on page 5 simply wouldn't be found by a search on page 1. A server-side search, on the other hand, searches the ENTIRE product catalog, independent of pagination.

filterGroups: Magento's search syntax

searchCriteria[filterGroups][0][filters][0][field]=name
searchCriteria[filterGroups][0][filters][0][value]=%25Shirt%25
searchCriteria[filterGroups][0][filters][0][condition_type]=like

condition_type=like combined with % wildcards in value (URL-encoded as %25) corresponds to a SQL "LIKE %Shirt%" search – finding "Shirt" ANYWHERE in the product name, not just at the start.

src/api/magentoApi.js
// ... previous code ...

export async function fetchProducts({
  page = 1,
  sortField = 'name',
  direction = 'ASC',
  query = '',
} = {}) {
  const params = new URLSearchParams({
    'searchCriteria[pageSize]': PAGE_SIZE,
    'searchCriteria[currentPage]': page,
    'searchCriteria[sortOrders][0][field]': sortField,
    'searchCriteria[sortOrders][0][direction]': direction,
  });

  if (query.trim() !== '') {
    params.set('searchCriteria[filterGroups][0][filters][0][field]', 'name');
    params.set('searchCriteria[filterGroups][0][filters][0][value]', `%${query}%`);
    params.set('searchCriteria[filterGroups][0][filters][0][condition_type]', 'like');
  }

  const data = await magentoFetch(`/products?${params.toString()}`);
  return {
    products: data.items,
    totalCount: data.total_count,
    pageCount: Math.ceil(data.total_count / PAGE_SIZE),
  };
}

components/SearchBar.jsx

src/components/SearchBar.jsx
import { useState } from 'react';

function SearchBar({ onSearch }) {
  const [input, setInput] = useState('');

  function submit(event) {
    event.preventDefault();
    onSearch(input);
  }

  return (
    <form onSubmit={submit}>
      <input
        type="text"
        placeholder="Search products..."
        value={input}
        onChange={(event) => setInput(event.target.value)}
      />
      <button type="submit">Search</button>
    </form>
  );
}

export default SearchBar;
src/pages/ProductListPage.jsx
import { useEffect, useState } from 'react';
import { fetchProducts } from '../api/magentoApi';
import Pagination from '../components/Pagination';
import ProductCard from '../components/ProductCard';
import SearchBar from '../components/SearchBar';

function ProductListPage() {
  const [products, setProducts] = useState([]);
  const [pageCount, setPageCount] = useState(1);
  const [currentPage, setCurrentPage] = useState(1);
  const [query, setQuery] = useState('');
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    setLoading(true);
    async function load() {
      const result = await fetchProducts({ page: currentPage, query });
      setProducts(result.products);
      setPageCount(result.pageCount);
      setLoading(false);
    }
    load();
  }, [currentPage, query]);

  function newSearch(term) {
    setQuery(term);
    setCurrentPage(1); // IMPORTANT: reset to page 1 on a new search
  }

  return (
    <div>
      <h1>Products</h1>
      <SearchBar onSearch={newSearch} />
      {loading ? (
        <p>Loading products...</p>
      ) : (
        <>
          <ul>
            {products.map((product) => (
              <ProductCard key={product.sku} product={product} />
            ))}
          </ul>
          <Pagination
            currentPage={currentPage}
            pageCount={pageCount}
            onPageChange={setCurrentPage}
          />
        </>
      )}
    </div>
  );
}

export default ProductListPage;

[currentPage, query] as dependencies ensure a new request fires both on page change AND on a new search – and setCurrentPage(1) in newSearch() prevents the subtle bug of landing on page 3 of a new, shorter result list that might only have 1 page.

Tipp: Search for a word fragment you'd expect in SEVERAL of your real product names – if only matching results show up, AND pagination works correctly within the filtered results, server-side search is working as intended.