24.08.2026・TechStuff
24.08.2026・TechStuff

Shopware 6 Hidden Gems #13: Why search can’t find AB-123.4 — tuning the tokenizer

Fabian Blechschmidt

„The search doesn’t find our article numbers.“ If you build shops for B2B or tech-adjacent merchants, you’ve had this ticket. The customer types AB-123.4 or K+S 40 or an email address into the search box, and the results are garbage. The usual reaction is „we need Elasticsearch“ or „we need a search plugin“. Sometimes true. But surprisingly often the fix is two lines of YAML nobody knows about, because the two config options — and the env var that goes with them — are documented nowhere.

What the tokenizer does to your search term

Every search term, and every product name during keyword indexing, goes through Tokenizer::tokenize() (Framework/DataAbstractionLayer/Search/Term/Tokenizer.php:21-55). The pipeline: lowercase, strip HTML, replace slashes with spaces, and then the interesting line —

$string = trim((string) preg_replace(
    \sprintf("/[^\pL%s0-9]/u", $allowChars), ' ', $string
));

Everything that is not a letter, a digit, or one of the preserved characters becomes a space, i.e. a token boundary. Afterwards, every token shorter than the minimum length is thrown away.

Both knobs are configurable:

# config/packages/shopware.yaml
shopware:
  search:
    preserved_chars: ['-', '_', '+', '.', '@', '/']
# .env
SHOPWARE_DBAL_TOKEN_MINIMUM_LENGTH=2

The defaults: preserved chars are ['-', '_', '+', '.', '@'] (packages/shopware.yaml:537), minimum token length is 3 — an env parameter with a default baked into the DI container (Framework/DependencyInjection/data-abstraction-layer.xml:11).

Reading the defaults like a support ticket

Once you know the mechanics, several classic search complaints diagnose themselves:

„Search for K+S 40 finds nothing“+ is preserved (good), but 40 has two characters. Minimum is three. The token is discarded, the search runs on k+s alone. Fix: SHOPWARE_DBAL_TOKEN_MINIMUM_LENGTH=2.

„Customers paste 1/2 Zoll from our catalog“/ is not preserved; it becomes a space by an explicit str_replace even before the regex. 1 and 2 then die at the minimum length. If your SKUs contain slashes, add / to preserved_chars and lower the minimum.

„Search for text/html… wait, why does that even work?“ — because slashes split into tokens that survive if they’re long enough. Tokenization is why some „weird“ searches accidentally work and similar ones don’t — the difference is rarely visible from the search box.

„Emails and domain names work fine“@ and . are preserved by default. Whoever picked the defaults thought about support staff searching customers by email address.

One nice safety net in the code (line 50-52): if all tokens get filtered out by the minimum length, the tokenizer returns the unfiltered tokens instead of nothing. A search for 40 alone still reaches the query — it’s the combination cases that silently degrade.

The part everyone gets wrong: reindex afterwards

Here is the trap. The same tokenizer runs in two places:

Change preserved_chars or the minimum length, and the two sides speak different dialects until you rebuild the keywords:

bin/console dal:refresh:index --only=product.indexer --use-queue

(Readers of part 2 of this series know the --only flag. Everyone else: it’s undocumented too.)

If you skip this, the change appears to „not work“ — or worse, works for newly saved products only, which is the kind of inconsistency that eats debugging afternoons.

Bonus knob: term_max_length

Same config corner, same documentation status: shopware.search.term_max_length (default 300) caps how long a search term may be before ProductSearchBuilder truncates it (Content/DependencyInjection/product.xml:322). Rarely a problem — until someone wires a barcode scanner or a paste-happy ERP frontend to the search box. If your logs show truncated-looking search terms, this is where the scissors live.

TL;DR

Shopware’s search tokenizer splits on everything that isn’t a letter, digit, or one of shopware.search.preserved_chars (default - _ + . @), then drops tokens shorter than SHOPWARE_DBAL_TOKEN_MINIMUM_LENGTH (default 3). SKUs with slashes or short segments silently fall apart. Fix the config for your assortment, then rebuild the search keywords with dal:refresh:index --only=product.indexer — the tokenizer runs at index time too, and mismatched sides is the classic way this fix „doesn’t work“. None of it documented.

Next up: WriteProtected and ApiAware — the two field flags that keep your sensitive entity fields out of the Store API without a single subscriber.

What’s the weirdest SKU format you’ve had to make searchable? Drop me an email!


Found in Shopware 6.7.0.0: tokenizer in Framework/DataAbstractionLayer/Search/Term/Tokenizer.php:21-55, defaults in Framework/Resources/config/packages/shopware.yaml:537 and Framework/DependencyInjection/data-abstraction-layer.xml:11 (env default 3), config nodes in Framework/DependencyInjection/Configuration.php:930-944, both-sides usage in Content/Product/SearchKeyword/ProductSearchKeywordAnalyzer.php and ProductSearchTermInterpreter.php, term cap wiring in Content/DependencyInjection/product.xml:322.