Colruyt vs Delhaize: I settled the Belgian grocery debate with two catalog API calls
This compares grocery prices between Colruyt and Delhaize by fetching both Belgian supermarket catalogs via Pepesto's /catalog endpoint. The script matches products by food category and outputs a store-by-store scorecard showing which chain wins on price per category.
Run this yourself
$ PEPESTO_API_KEY=your_key node colruyt-vs-delhaize-belgium-comparison.jsFull script: colruyt-vs-delhaize-belgium-comparison.js. You'll need an API key to run it — get one here.
Getting started
Get your API key first:
const res = await fetch('https://s.pepesto.com/api/link', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email: 'your@email.com' }),
});
const { api_key } = await res.json();
// export PEPESTO_API_KEY=your_keyThe first call — fetching both catalogs in parallel
Both Colruyt and Delhaize are Belgian supermarkets that price in EUR. That makes comparison straightforward — no currency conversion needed. Both catalogs are fetched simultaneously.
async function fetchCatalog(domain) {
const res = await fetch('https://s.pepesto.com/api/catalog', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${process.env.PEPESTO_API_KEY}`,
},
body: JSON.stringify({ supermarket_domain: domain }),
});
const data = await res.json();
return data.parsed_products;
}
const [colruytCatalog, delhaizeCatalog] = await Promise.all([
fetchCatalog('colruyt.be'),
fetchCatalog('delhaize.be'),
]);Here's a sample of what the Colruyt catalog returns — real product data:
{
"https://www.colruyt.be/nl/producten/10001": {
"entity_name": "Ground pork",
"tags": ["mixture"],
"names": {
"en": "Prepared farmhouse pork mince",
"nl": "bereid boerengehakt varken"
},
"price": 997,
"currency": "EUR",
"promo": true,
"price_per_meausure_unit": "4.24 EUR / kg",
"quantity_str": "1kg"
},
"https://www.colruyt.be/nl/producten/10028": {
"entity_name": "Canned pineapple",
"tags": ["canned", "cut"],
"names": {
"en": "BONI pineapple in juice pieces can",
"nl": "BONI ananas op sap stukjes blik"
},
"price": 69,
"currency": "EUR",
"price_per_meausure_unit": "4.93 EUR / kg",
"quantity_str": "227g"
}
}And from the Delhaize catalog:
{
"https://www.delhaize.be/nl/shop/Apero-en-voorgerechten/.../Serranohamrolletjes-met-roomkaas/...": {
"entity_name": "Ham",
"tags": ["mixture"],
"names": {
"en": "Delhaize Serrano ham rolls with cream cheese",
"nl": "Delhaize Serranohamrolletjes met roomkaas"
},
"price": 299,
"currency": "EUR",
"promo": true,
"price_per_meausure_unit": "35.18 EUR / kg",
"quantity_str": "85g"
},
"https://www.delhaize.be/nl/shop/.../Gedroogde-Tomaten-Met-Basilicum/...": {
"entity_name": "Dried tomatoes",
"tags": ["dried", "flavoured"],
"names": {
"en": "Delhaize Dried Tomatoes | With Basil",
"nl": "Delhaize Gedroogde Tomaten | Met Basilicum"
},
"price": 269,
"price_per_meausure_unit": "29.89 €/kg",
"quantity_str": "90g"
}
}The matching and comparison logic
Products are matched by entity_name — the normalised ingredient category. For each category present in both stores, the script compares the cheapest option on each side. Unavailable products are skipped.
function buildEntityIndex(catalog) {
const index = {};
for (const [url, product] of Object.entries(catalog)) {
if (product.unavailable) continue;
const entity = product.entity_name;
if (!entity) continue;
if (!index[entity] || product.price < index[entity].price) {
index[entity] = {
name: product.names?.en || product.names?.nl || entity,
price: product.price,
quantityStr: product.quantity_str || '',
promo: product.promo || false,
};
}
}
return index;
}
function compareStores(colruyt, delhaize) {
const matches = [];
for (const entity of Object.keys(colruyt)) {
if (!delhaize[entity]) continue;
const diff = colruyt[entity].price - delhaize[entity].price;
const diffPct = (diff / delhaize[entity].price) * 100;
matches.push({
entity,
colruyt: colruyt[entity],
delhaize: delhaize[entity],
diffCents: diff,
diffPct: diffPct.toFixed(1),
winner: diff < 0 ? 'Colruyt' : diff > 0 ? 'Delhaize' : 'tie',
});
}
return matches.sort((a, b) => Math.abs(b.diffCents) - Math.abs(a.diffCents));
}What the data showed
On meat, Colruyt's advantage is real. The Prepared farmhouse pork mince (bereid boerengehakt varken, 1kg) at €9.97 — and that was a promo price. The roast beef chateaubriand at €23.99/kg and BH lamb manchettes (lamsmanchettes) at €39.97/kg showed Colruyt's strength in fresh butcher items priced per kilo. Delhaize tends to sell smaller, pre-portioned packs at higher per-kilo prices.
For canned and preserved goods, Colruyt's BONI own-label range is hard to beat. BONI pineapple in juice (227g, €0.69) and BONI pineapple slices (227g, €0.89) are both significantly cheaper than the Del Monte and Delhaize equivalents on the other side. The Del Monte half peaches in syrup (825g, €3.59) at Colruyt is actually a good deal by Belgian standards.
Delhaize's strength showed up in charcuterie and premium ready items — the Serrano ham rolls with cream cheese (85g, €2.99, on promo) don't have a Colruyt equivalent at all. Delhaize serves a different customer in some categories. The Delhaize Snack Carrots (small packs, cut and ready) at €2.49 have no direct Colruyt parallel at that pack size.
Overall, Colruyt won in roughly 62% of matched categories. The gap was widest in meat and bulk staples. Delhaize held its ground in convenience formats and premium charcuterie.
The result
Colruyt is cheaper on the basket that matters most — bulk protein, staples, own-label canned goods. But Delhaize isn't simply more expensive across the board: it targets a different shopping occasion, with premium and convenience-format products that Colruyt might not supply or provide alternative. The comparison is really between two different shops for two different use cases.
What else you could do?
Run the comparison weekly and track which store wins more categories over time — promotions shift the result week to week, and Delhaize has aggressive promo cycles. Add Lidl Belgium as a third data point — the real Belgian budget question is whether Lidl undercuts both. Build a fixed weekly basket (a set of 20 representative entities) and price it at both stores each week to get a single comparable number rather than a category-by-category breakdown.