Skip to content

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:

EndpointReturnsUse when
GET /search/suggestRendered Liquid section HTMLYou want the theme’s predictive-search section markup, ready to drop into the DOM
GET /search/suggest.jsonA JSON map of matched resourcesYou 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.

ParameterAliasesDefaultNotes
q(required)The search terms. An empty q is rejected with HTTP 422.
resources[type]typequery,product,collection,pageComma-separated list of resource types to return.
resources[limit]limit10Results to return, 110. Out-of-range values return 422.
resources[limit_scope]limit_scopeallall 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]=6

JSON 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:

PropertyDescription
predictive_search.performedtrue once a non-empty query has run — gate your “no results” UI on this.
predictive_search.termsThe submitted search terms.
predictive_search.resourcesThe result buckets: products, collections, pages, articles, queries.
predictive_search.typesThe 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.

StatusWhenHTML bodyJSON body
422Missing/empty q, invalid type, limit, or limit_scopeInvalid parameter error: …{ "status": "422", "message": "Invalid parameter error", "description": "…" }
422Missing section_id (HTML mode only)Invalid parameter error: Missing section_id param
404Section id not found in the themeSection not found: Section id … was not found in the theme
500Unexpected server errorInternal 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

  1. Read the base path once from {{ routes.predictive_search_url }} and store it on the input element (or in JS).

  2. Debounce the input. On each settled keystroke, abort any in-flight request and skip empty queries.

  3. Fetch either /search/suggest?section_id=predictive-search&q=… for ready-made HTML, or /search/suggest.json?q=… for raw resources.

  4. Render the overlay — innerHTML-swap the section, or build markup from the resources.results buckets.

  5. 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.

Next steps