Shopware 6 Hidden Gems #24: Lightning round — ten small gems that didn’t need their own post
Fabian Blechschmidt
Twenty-three parts into this series, my research folder still has a stack of finds that are real, useful, undocumented — and about two paragraphs big. Instead of stretching each into a full post, here’s the lightning round. One problem, one gem, one file reference. Go.
1. rawUrl() — URLs for mail templates that actually work
Twig’s url() inside a mail template or CLI context generates paths relative to the sales channel’s virtual base URL — often wrong outside a storefront request. rawUrl() strips the base path and takes an optional domain (Framework/Adapter/Twig/Extension/RawUrlFunctionExtension.php:33-50):
Absolute, correct, works in emails. The domain parameter is the undocumented part.
2. forwardToRoute() — delegate without redirecting
StorefrontController::forwardToRoute('frontend.home.page', [...]) runs another route’s controller inside the current request — no 302, URL stays put (StorefrontController.php:146-158). The core uses it for „render this other page but keep my URL“ cases like error handling in forms. Note it forces GET matching — it’s for displaying results, not for POST passthrough (the code comments say so verbatim).
3. MetaInformation — per-page robots directives
The page struct everyone uses for metaTitle also carries robots, canonical, xmlLang, copyrightYear and revisit (storefront/Page/MetaInformation.php:11-27). Setting noindex,follow on filtered listing pages from a subscriber is three lines — no template override:
4. Elasticsearch EXPLAIN_MODE — score debugging per request
Add the context state ElasticsearchEntitySearcher::EXPLAIN_MODE and the ES query runs with include_named_queries_score and track_scores (elasticsearch/.../ElasticsearchEntitySearcher.php:27,75-77) — every hit tells you which queries matched it and what they contributed. „Why does product X rank above Y“ becomes readable data instead of vibes.
5. shopware.mail.max_body_length — mails that fail loudly
Default 0 (unlimited). Set it, and any mail whose rendered body exceeds the limit throws MailException::mailBodyTooLong instead of being silently mangled or rejected downstream by your mail provider (Content/Mail/Service/MailSender.php:61-63). If your transactional mails embed order tables that can explode in size, a limit plus monitoring beats a customer telling you their confirmation never arrived.
6. shopware.dompdf.options — the PDF engine takes orders
Invoice PDFs come out of Dompdf, and the whole Dompdf Options array is exposed as config (Checkout/Document/Service/PdfRenderer.php:59-63). Custom font directory, isRemoteEnabled, chroot paths — all YAML instead of decorating the renderer:
7. shopware.api.access_token_ttl — OAuth lifetimes are config
Admin API access tokens live 10 minutes, refresh tokens a week — both are shopware.api.access_token_ttl / refresh_token_ttl in DateInterval syntax (Framework/DependencyInjection/Configuration.php:187-188, wired through api.xml into the OAuth listener). Long-running warehouse tablets that keep logging out? Now you know which knob exists — and which trade-off you’re making when you turn it.
8. Theme compilation skip states — fast plugin deployments
Three context states short-circuit the expensive parts of plugin lifecycle operations: ThemeLifecycleHandler::STATE_SKIP_THEME_COMPILATION, ThemeService::STATE_NO_QUEUE (force synchronous compile instead), and PluginLifecycleService::STATE_SKIP_ASSET_BUILDING. Updating ten plugins in a deployment? Skip compilation ten times, compile once at the end. (The related env var SHOPWARE_SKIP_ASSET_INSTALL made it into the docs; the states didn’t.)
9. plugin:list --json --filter — plugin state for machines
bin/console plugin:list --json dumps the full plugin entities as JSON; --filter payment narrows by name/label (Framework/Plugin/Command/PluginListCommand.php:41-48). Pipe through jq in CI and you get drift detection between staging and production for free:
The human-readable table, by the way, has a column the JSON output makes explicit: whether each plugin is managed by Composer — handy while migrating a shop to composer-managed plugins.
10. HTTP cache cookies — sw-cache-hash and sw-states
Not a feature to use, but a mechanism to understand when debugging cache hit rates: Shopware partitions its HTTP cache by cookie. sw-cache-hash encodes the customer’s context (currency, tax state, rule matches) and takes priority; sw-states marks conditions like logged-in/cart-filled that interact with the _httpCache route states from part 20 (Framework/Adapter/Cache/Http/HttpCacheKeyGenerator.php:18-21, cookie logic in CacheResponseSubscriber.php:100-121). If your cache hit rate cratered after a plugin started assigning every visitor a rule-matching context: this is where to look.
TL;DR
Ten small, verified, undocumented tools: mail-safe rawUrl(), redirect-free forwardToRoute(), per-page robots via MetaInformation, ES score explanations per context state, a mail size guard, Dompdf options in YAML, OAuth TTL config, theme-compilation skip states, plugin:list --json for CI, and the cache-partitioning cookies. None deserves a whole post; all deserve to be known.
This concludes the deep-dive backlog from my source scan — if the series helped you, the best thanks is a hidden gem I don’t know yet. Drop me an email!
All references from Shopware 6.7.0.0; file paths and line numbers per item above.