Technical guides with real code, not empty promises. Each integration covers two things: showing Heurix results instead of native search, and keeping your catalog in sync.
Honesty first, and the answer differs by platform. PrestaShop has a paid module, sold on PrestaShop Addons since September 11, 2026 — see its page. WooCommerce has a plugin published on wordpress.org, installable from the WordPress admin — see its page. Shopify has an app, submitted to App Store review on September 13, 2026, which Shopify has neither approved nor rejected to date — see its page. Magento has the guide below only, and the Shopify guide has not been proven on a real store. The PrestaShop module and the WooCommerce plugin both install from their platform's official store today, and the code that follows remains the route if you would rather wire search up yourself. What the response carries, and the product URL your front end composes: headless search. Corrected on September 28, 2026: this note said “only the PrestaShop module installs from its platform's official store”, true until the WooCommerce plugin was published on wordpress.org that day. Corrected on September 19, 2026: since September 6, this note said Shopify had the guide only, and, after the PrestaShop module was approved on Addons on September 11, that this module was in beta and that none of the four installed from an official store.
Exporting your catalog from an ERP or a PIM? Drop the file in your console, in CSV or XML: the structure is detected, field matching is suggested, and the rule pack is recommended based on your content. See how it works →
The relay (Cloudflare Worker):
// worker.js — déployé sur Cloudflare Workers (gratuit pour ce volume)
export default {
async fetch(request, env) {
const url = new URL(request.url);
const q = url.searchParams.get("q") || "";
const res = await fetch("https://api.heurix.fr/v1/index/" + env.HEURIX_CATALOG + "/search", {
method: "POST",
headers: {
"Authorization": "Bearer " + env.HEURIX_API_KEY, // secret Cloudflare, jamais exposé
"Content-Type": "application/json"
},
body: JSON.stringify({ q, limit: 20 })
});
return new Response(await res.text(), {
headers: { "Content-Type": "application/json", "Access-Control-Allow-Origin": "https://votre-boutique.myshopify.com" }
});
}
}
In the theme (Online Store 2.0), replace the rendering of sections/search-results.liquid with a call to this relay:
<div id="heurix-results"></div>
<script>
const params = new URLSearchParams(window.location.search);
fetch("https://votre-worker.workers.dev/search?q=" + encodeURIComponent(params.get("q") || ""))
.then(r => r.json())
.then(data => {
document.getElementById("heurix-results").innerHTML = data.hits.map(h =>
`<a href="/products/${h.product.handle}" class="heurix-hit">
<span>${h.product.name}</span><span>${h.product.price} €</span>
</a>`
).join("");
});
</script>
Register a Shopify webhook (Admin API or shopify.app.toml for a custom app) on the products/create, products/update, products/delete topics, pointed at the same Worker:
// Ajout dans worker.js : gestion du webhook Shopify
async function handleWebhook(request, env) {
const product = await request.json();
await fetch("https://api.heurix.fr/v1/index/" + env.HEURIX_CATALOG + "/items", {
method: "POST",
headers: { "Authorization": "Bearer " + env.HEURIX_API_KEY, "Content-Type": "application/json" },
body: JSON.stringify({
rulepack: "mode",
items: [{
id: String(product.id),
name: product.title,
ref: product.variants?.[0]?.sku || "",
description: product.body_html?.replace(/<[^>]+>/g, "") || "",
stock: product.variants?.reduce((s, v) => s + (v.inventory_quantity || 0), 0) || 0,
handle: product.handle,
price: product.variants?.[0]?.price
}]
})
});
}
Remember to verify the Shopify webhook's HMAC signature (X-Shopify-Hmac-Sha256) before processing the payload, to avoid accepting forged requests.
In a custom module, hook displaySearchResults or override the search controller:
<?php
// modules/heurixsearch/heurixsearch.php
class HeurixSearch extends Module
{
public function search($query)
{
$ch = curl_init('https://api.heurix.fr/v1/index/' . Configuration::get('HEURIX_CATALOG') . '/search');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . Configuration::get('HEURIX_API_KEY'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode(['q' => $query, 'limit' => 24]),
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
return $response['hits'] ?? [];
}
}
In the results template (search.tpl), loop over these results instead of PrestaShop's native search.
Three hooks are enough to stay in sync in real time:
<?php
public function install()
{
return parent::install()
&& $this->registerHook('actionProductAdd')
&& $this->registerHook('actionProductUpdate')
&& $this->registerHook('actionProductDelete');
}
public function hookActionProductUpdate($params)
{
$product = $params['product'];
$this->pushToHeurix([
'id' => (string) $product->id,
'name' => $product->name,
'ref' => $product->reference,
'description' => strip_tags($product->description_short),
'stock' => (int) StockAvailable::getQuantityAvailableByProduct($product->id),
]);
}
private function pushToHeurix($item)
{
$ch = curl_init('https://api.heurix.fr/v1/index/' . Configuration::get('HEURIX_CATALOG') . '/items');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . Configuration::get('HEURIX_API_KEY'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode(['rulepack' => 'mode', 'items' => [$item]]),
]);
curl_exec($ch);
curl_close($ch);
}
wp_remote_post(), WordPress's native HTTP function, rather than raw cURL.
<?php
// Dans functions.php ou un plugin dédié
add_filter('woocommerce_product_query_meta_query', function ($meta_query, $query) {
if (!is_search()) return $meta_query;
$response = wp_remote_post('https://api.heurix.fr/v1/index/' . HEURIX_CATALOG . '/search', [
'headers' => [
'Authorization' => 'Bearer ' . HEURIX_API_KEY,
'Content-Type' => 'application/json',
],
'body' => json_encode(['q' => get_search_query(), 'limit' => 24]),
]);
$hits = json_decode(wp_remote_retrieve_body($response), true)['hits'] ?? [];
$ids = array_map(fn($h) => (int) $h['product']['id'], $hits);
// Réordonne les résultats WooCommerce natifs selon le classement Heurix
$query->set('post__in', $ids ?: [0]);
$query->set('orderby', 'post__in');
return $meta_query;
}, 10, 2);
This approach has an advantage: it reuses WooCommerce's native display (product cards, existing filters), Heurix only reranks results — less template to rewrite.
<?php
add_action('woocommerce_update_product', 'heurix_sync_product');
add_action('woocommerce_new_product', 'heurix_sync_product');
function heurix_sync_product($product_id) {
$product = wc_get_product($product_id);
wp_remote_post('https://api.heurix.fr/v1/index/' . HEURIX_CATALOG . '/items', [
'headers' => [
'Authorization' => 'Bearer ' . HEURIX_API_KEY,
'Content-Type' => 'application/json',
],
'body' => json_encode([
'rulepack' => 'mode',
'items' => [[
'id' => (string) $product_id,
'name' => $product->get_name(),
'ref' => $product->get_sku(),
'description' => wp_strip_all_tags($product->get_short_description()),
'stock' => $product->get_stock_quantity() ?? 0,
]],
]),
]);
}
add_action('woocommerce_delete_product', function ($product_id) {
wp_remote_request('https://api.heurix.fr/v1/index/' . HEURIX_CATALOG . '/items/' . $product_id, [
'method' => 'DELETE',
'headers' => ['Authorization' => 'Bearer ' . HEURIX_API_KEY],
]);
});
A custom module with a plugin on the search results block:
<?php
// app/code/Heurix/Search/Model/SearchClient.php
namespace Heurix\Search\Model;
use Magento\Framework\HTTP\Client\Curl;
class SearchClient
{
public function __construct(private Curl $curl) {}
public function search(string $query): array
{
$this->curl->addHeader('Authorization', 'Bearer ' . HEURIX_API_KEY);
$this->curl->addHeader('Content-Type', 'application/json');
$this->curl->post(
'https://api.heurix.fr/v1/index/' . HEURIX_CATALOG . '/search',
json_encode(['q' => $query, 'limit' => 24])
);
$data = json_decode($this->curl->getBody(), true);
return $data['hits'] ?? [];
}
}
Inject this service into a plugin on Magento\CatalogSearch\Model\Layer\Filter, or directly replace the search results template with rendering based on this data.
An observer on the catalog's standard events:
<?php
// app/code/Heurix/Search/Observer/SyncProduct.php
namespace Heurix\Search\Observer;
use Magento\Framework\Event\ObserverInterface;
use Magento\Framework\Event\Observer;
class SyncProduct implements ObserverInterface
{
public function __construct(private \Magento\Framework\HTTP\Client\Curl $curl) {}
public function execute(Observer $observer)
{
$product = $observer->getEvent()->getProduct();
$this->curl->addHeader('Authorization', 'Bearer ' . HEURIX_API_KEY);
$this->curl->addHeader('Content-Type', 'application/json');
$this->curl->post('https://api.heurix.fr/v1/index/' . HEURIX_CATALOG . '/items', json_encode([
'rulepack' => 'mode',
'items' => [[
'id' => (string) $product->getId(),
'name' => $product->getName(),
'ref' => $product->getSku(),
'stock' => (int) $product->getExtensionAttributes()?->getStockItem()?->getQty(),
]],
]));
}
}
Declared in events.xml on catalog_product_save_after and catalog_product_delete_after.
These guides are a technical starting point, not a turnkey module. If your setup is out of the ordinary, get in touch.