Predictive Search API
The Predictive Search API powers the search-as-you-type overlay — the dropdown of suggested products, collections, pages, and search terms that appears as a shopper types. It lives on the storefront’s own domain and is Shopify-compatible, so search code written for Shopify themes generally ports across.
There are two ways to call it, sharing one endpoint:
| Endpoint | Returns | Use when |
|---|---|---|
GET /search/suggest | Rendered Liquid section HTML | You want the theme’s predictive-search section markup, ready to drop into the DOM |
GET /search/suggest.json | A JSON map of matched resources | You want raw results to render in your own JavaScript |
Always build the base path from the routes object rather than hard-coding it:
<input data-predictive-search-url="{{ routes.predictive_search_url }}">routes.predictive_search_url resolves to /search/suggest — and on a localized
store it carries the locale prefix automatically (for example /en/search/suggest),
so a single line works across every locale you ship.
Query parameters
Both modes accept the same query parameters. Shopify’s bracketed
resources[...] form and the flat aliases are both honoured.
| Parameter | Aliases | Default | Notes |
|---|---|---|---|
q | — | (required) | The search terms. An empty q is rejected with HTTP 422. |
resources[type] | type | query,product,collection,page | Comma-separated list of resource types to return. |
resources[limit] | limit | 10 | Results to return, 1–10. Out-of-range values return 422. |
resources[limit_scope] | limit_scope | all | all caps the total across types; each applies the limit per type. |
section_id | — | (required for HTML mode) | The Liquid section id to render. Only used by /search/suggest. |
The supported resource types are product, collection, page, article,
and query. query returns suggested completed search terms rather than a
resource — the “did you mean” style phrases that link to the full results page.
Any unrecognised type returns an Invalid option for type parameter error, so
validate the list you pass.
/search/suggest.json?q=jacket&resources[type]=product,collection,query&resources[limit]=6JSON mode
GET /search/suggest.json returns the matched resources directly. Request it,
then build your own markup:
async function suggest(terms) { const base = document .querySelector('[data-predictive-search-url]') ?.dataset.predictiveSearchUrl || '/search/suggest';
const params = new URLSearchParams({ q: terms }); params.set('resources[type]', 'product,collection,query'); params.set('resources[limit]', '6');
const res = await fetch(`${base}.json?${params}`, { headers: { Accept: 'application/json' }, }); if (!res.ok) return null; return (await res.json()).resources.results;}The response is always shaped the same way — the five result buckets are present even when a type was not requested (they come back empty):
{ "resources": { "results": { "queries": [ { "text": "jacket", "styled_text": "jacket", "url": "/search?q=jacket" } ], "products": [ /* product objects */ ], "collections": [ /* collection objects */ ], "pages": [], "articles": [] } }}Each entry in queries carries text (the suggested phrase), styled_text,
and a ready-to-use url that points at the full search results page.
The products, collections, pages, and articles arrays contain the same
objects your Liquid templates use — read title, url, featured_image, price
fields, and so on. See the Liquid objects reference
for the full field list per resource.
Section-rendered mode
GET /search/suggest returns the theme’s own predictive-search section,
rendered with the live results. This is the path most themes use: the markup,
classes, and styling all come from your Liquid, so the overlay matches the
storefront without you re-templating results in JavaScript.
Pass section_id to name the section to render. The response is the section’s
HTML wrapped in a Shopify-style container:
<div id="shopify-section-predictive-search"> … </div>Fetch it and swap the inner HTML into your overlay:
async function renderSuggestions(terms, container) { const params = new URLSearchParams({ q: terms }); params.set('section_id', 'predictive-search'); params.set('resources[type]', 'product,query');
const res = await fetch(`/search/suggest?${params}`); if (!res.ok) return;
const html = await res.text(); const doc = new DOMParser().parseFromString(html, 'text/html'); const section = doc.querySelector('#shopify-section-predictive-search'); container.innerHTML = section ? section.innerHTML : '';}How the section consumes the results
Inside the predictive-search section, the results are available on the
predictive_search Liquid object — the same data the JSON endpoint returns,
but rendered server-side. A minimal section loops over the resource buckets:
{% if predictive_search.performed and predictive_search.resources.products.size > 0 %} <ul role="listbox"> {% for product in predictive_search.resources.products %} <li> <a href="{{ product.url }}"> {{ product.featured_image | image_url: width: 80 | image_tag }} {{ product.title }} </a> </li> {% endfor %} </ul>{% endif %}
{% for query in predictive_search.resources.queries %} <a href="{{ query.url }}">{{ query.styled_text }}</a>{% endfor %}Key properties on the object:
| Property | Description |
|---|---|
predictive_search.performed | true once a non-empty query has run — gate your “no results” UI on this. |
predictive_search.terms | The submitted search terms. |
predictive_search.resources | The result buckets: products, collections, pages, articles, queries. |
predictive_search.types | The resource types that were requested. |
See the predictive_search object reference
for the complete property list.
Errors
Errors are returned in the format matching the endpoint you called — plain text
for /search/suggest, JSON for /search/suggest.json.
| Status | When | HTML body | JSON body |
|---|---|---|---|
422 | Missing/empty q, invalid type, limit, or limit_scope | Invalid parameter error: … | { "status": "422", "message": "Invalid parameter error", "description": "…" } |
422 | Missing section_id (HTML mode only) | Invalid parameter error: Missing section_id param | — |
404 | Section id not found in the theme | Section not found: Section id … was not found in the theme | — |
500 | Unexpected server error | Internal server error | { "status": "500", "message": "Internal Server Error", … } |
Treat a non-2xx as “show nothing” — clear the overlay and let the shopper keep
typing rather than surfacing a raw error.
Putting it together
-
Read the base path once from
{{ routes.predictive_search_url }}and store it on the input element (or in JS). -
Debounce the input. On each settled keystroke, abort any in-flight request and skip empty queries.
-
Fetch either
/search/suggest?section_id=predictive-search&q=…for ready-made HTML, or/search/suggest.json?q=…for raw resources. -
Render the overlay —
innerHTML-swap the section, or build markup from theresources.resultsbuckets. -
Make each query suggestion link to
query.url, and let pressing Enter submit the full search at{{ routes.search_url }}.
Best when you want the overlay styled by the theme. The predictive-search
section owns the layout; your JS only swaps HTML in and out.
Best when you render results in a framework or a custom component. You control the markup and read fields straight off the resource objects.
Next steps
- Cart AJAX API — the sibling storefront API, with
the same bundled-section-rendering pattern (
shopify-section-…wrappers). - Product Recommendations API for “you may also like” and cart cross-sells.
- Liquid objects — the
predictive_search,product, andcollectionobjects this API returns. - Search and discovery — configure what your storefront search actually matches and ranks.
- Section schema and
Theme architecture for building the
predictive-searchsection you render through this API.