„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 —
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.
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_charsand 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:
Query side:ProductSearchTermInterpreter tokenizes what the customer typed — takes effect immediately.
Index side:ProductSearchKeywordAnalyzer tokenized your product names when the keyword index was built.
Change preserved_chars or the minimum length, and the two sides speak different dialects until you rebuild the keywords:
(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.