Overlays

Correct the catalog's data for search, add search words or give products badges, and keep it all when the next export arrives.

An overlay corrects catalog data for search, or gives it badges, without touching the catalog. It lives in the release, so it stays when the next upload arrives. The home store marks its three bestsellers this way, such as the sofa SOFA-ASKA-2:

tests/fixtures/home/overlays.json (trimmed)
{
    "overlays": [
        {
            "id": "bestseller-sofa-aska-2",
            "target": { "type": "product", "id": "SOFA-ASKA-2" },
            "badges": { "de": ["Bestseller"], "en": ["Best seller"] },
            "note": "One of the three products that sold most in the last 30 days"
        }
    ]
}

The product lookup answers with the badge in the request's language:

Terminal
curl -s 'http://127.0.0.1:7800/v1/product?id=SOFA-ASKA-2' | jq -c '.product | {id, badges}'
curl -s 'http://127.0.0.1:7800/v1/product?id=SOFA-ASKA-2&locale=en' | jq -c '.product.badges'
200 · 2.4 ms · release b25a6aaa
{"id":"SOFA-ASKA-2","badges":["Bestseller"]}
["Best seller"]

A search hit carries the badges too where its type's result lists badges, as the home store's product type does. note is for the people who keep the file.

Reference: badges · Product.badges · result

Fill what the catalog lacks

fill writes an attribute value only where a product has none. Three of the home store's four sofas name no room, so this overlay gives them Wohnzimmer (living room):

overlays.json
{
    "id": "sofas-wohnzimmer",
    "target": { "type": "product", "where": { "category": { "within": "wohnen/sofas" } } },
    "fill": { "attributes": { "room": "Wohnzimmer" } }
}

target names one entity by its id or every entity of its type that matches where, never both. where is a selector over attributes, the category and fields such as brand or price. Values are read as the export's are, so "zwei Meter" (two meters) for a width is a violation, not a value that vanishes.

Once the export names a sofa's room, fill steps aside for it. set, written as "set": { "attributes": { "room": "Wohnzimmer" } }, replaces the export's values instead, the variants' own included.

Reference: target · fill · set · Selector

Add search words

keywords adds search terms to a product's own. A shopper who types "Balkonteppich" (balcony rug) misses the outdoor rug TEPPICH-MARE. The why-not answer, which the Playground shows under Why isn't it here?, says what to do:

Terminal
curl -s -H "Authorization: Bearer $key" -H 'Content-Type: application/json' \
  -d '{"request": {"query": "Balkonteppich"}, "entity": "product:TEPPICH-MARE"}' $api/preview/find \
  | jq -r '.sentence, .next[].step'
200 · 15 ms · release b25a6aaa
The query's words do not find it: no searched field of it holds “balkonteppich”, as typed, through a synonym or with a typo. Add a synonym that leads “balkonteppich” to what it holds, or add it to its search words.
add_synonym
add_keywords

A synonym would lead the word to what another word finds, such as every rug. A keyword adds it to this rug alone:

overlays.json
{
    "id": "mare-balkonteppich",
    "target": { "type": "product", "id": "TEPPICH-MARE" },
    "keywords": { "de": ["Balkonteppich"] }
}

A plain list instead of the map adds the words in every language.

Reference: keywords · POST /api/v1/preview/find

Hide a product everywhere

hide takes its targets out of every request in every channel: search, category pages and the product lookup alike.

overlays.json
{ "id": "hide-sessel-tarn", "target": { "type": "product", "id": "SESSEL-TARN" }, "hide": true }

To hide a product from some searches or pages only, write a rule instead, and to leave it out of one channel, change that channel's assortment. When a shopper misses a hidden product, the why-not answer names the overlay.

Reference: hide

End at a time

until ends an overlay at a moment, and the search is rebuilt without it then, on its own. This badge, "Neu" (new), lasts until December:

overlays.json
{
    "id": "neu-stuhl-riga",
    "target": { "type": "product", "id": "STUHL-RIGA" },
    "badges": { "de": ["Neu"], "en": ["New"] },
    "until": "2026-12-01T00:00:00+01:00"
}

Without until, an overlay holds until you remove it. One whose product left the catalog waits, and applies again when it returns.

Reference: until

How a change goes live

Overlays change what the search's documents are built from, so publishing a change to overlays.json rebuilds the search, even for a note. Add the sofa overlay to a draft and ask how it would go live:

Terminal
draft=$(curl -s -X POST -H "Authorization: Bearer $key" -H 'Content-Type: application/x-ndjson' \
  --data-binary @tests/fixtures/home/catalog.jsonl "$api/imports?name=catalog.jsonl" | jq -r .import.id)
version=$(curl -s -H "Authorization: Bearer $key" $api/imports/$draft | jq -r .version)
jq --arg version "$version" '{version: $version, files: {"overlays.json": (.overlays += [{id: "sofas-wohnzimmer",
      target: {type: "product", where: {category: {within: "wohnen/sofas"}}}, fill: {attributes: {room: "Wohnzimmer"}}}])}}' \
    tests/fixtures/home/overlays.json \
  | curl -s -X PATCH -H "Authorization: Bearer $key" -H 'Content-Type: application/json' -d @- $api/imports/$draft >/dev/null
curl -s -H "Authorization: Bearer $key" $api/imports/$draft/publication | jq -r .course.sentence
200 · 23 ms
Rebuilds the search: overlays.json changed what its documents are built from.

A draft's file replaces the published one whole, so it holds the home store's three badges and the new overlay. Publishing builds new indexes beside the live ones while the shop keeps answering (Releases). This draft is discarded:

Terminal
curl -s -X DELETE -H "Authorization: Bearer $key" $api/imports/$draft | jq -r .state
200 · 18 ms
discarded

Reference: overlays.json · PATCH /api/v1/imports/{import}

Next

  • Rules: hide, pin or bury a product for some searches and pages only.
  • Importing and mapping: fix how the export itself is read.
  • Releases: preview, publish and roll back the draft.

On this page