Optimizing Shopware Rule Builder for Fast, Flexible Pricing Without Code
Learn how to design efficient Shopware Rule Builder rules for pricing, avoid performance pitfalls, and verify results with concrete examples.
19 May 2026, 20:13 UTC

The Checkout Slowdown: A Real‑World Pricing Dilemma
A mid‑size retailer noticed that their checkout page began to lag after adding three promotional rules. The cart contains 20 line items, and the total amount exceeds €100. Customers see a delayed response, and support tickets about "slow checkout" rose sharply. The business needed a way to apply dynamic discounts without writing PHP code, but the performance hit threatened the user experience.
The takeaway is clear: Shopware’s Rule Builder can deliver the needed flexibility, yet only when rules are kept shallow and efficiently compiled.
How the Rule Builder Works Under the Hood
The Rule Builder stores conditions as a tree of AND/OR groups. Each node uses operators such as equals, greater than, one of, or date ranges. At evaluation time the tree is traversed against a typed Context object that contains the cart, customer, sales channel, and any custom fields.
Behind the scenes, Shopware compiles each rule into a PHP AST and caches it as an executable closure. The cache key includes the rule ID and its version, so changing a rule invalidates only the affected compilations without flushing the entire cache.
Performance scales with two factors: the number of rules and the number of cart line items. A typical 50‑rule set on a 20‑line cart evaluates in 2–5 ms. Complex nested rules that require database lookups (e.g., “customer ordered X in the last 30 days”) can add 10–30 ms per rule, which quickly becomes noticeable at checkout.
Designing Efficient Rules: Best Practices
- Keep rule depth low. Avoid more than three nested OR levels; deeper trees increase recursion depth and may hit PHP’s
xdebug.max_nesting_levellimit. - Batch DB access. Custom conditions that run queries inside the evaluation loop cause N+1 problems. Use
RuleScope::getContext()->getState()or theRuleLoaderservice to pre‑fetch data. - Manual versioning. There is no built‑in draft/publish workflow. Create a custom rule draft entity and use Flow Builder to promote a rule when it is ready, or simply clone the rule before editing.
- Respect immutable context. The sales channel context cannot change during a single evaluation. If you need different currencies or languages per channel, create separate rules for each channel.
- Test headless evaluation. Use the Rule API endpoint
POST /api/search/rulewith a simulated cart context to verify that the returned rule IDs match storefront behavior without a full checkout session.
Worked Example: Tiered Discount Based on Cart Value and Customer Tier
Goal: Offer 5 % off for carts over €100 and an additional 2 % off when the customer belongs to the "Gold" tier.
- In the Rule Builder UI, create a new rule titled Gold Tier Tiered Discount.
- Condition 1 (AND group):
Cart amount > 100using thegreater thanoperator. - Condition 2 (AND group):
Customer tier = Gold. For custom fields, add aRuleConditionservice that exposescustomer.customFields.erpTieras a first‑class criterion. - Action: Apply a
Cart price rulewith a 7 % discount (5 % + 2 %). - Save the rule; the system creates a new version, automatically invalidating the previous cached closure.
To verify, clear the cache:
bin/console cache:clear
Then place a test cart with a total of €120 and assign the Gold tier to the customer. Use the Storefront cart preview or call:
POST /api/checkout/cart/line-item
Check that the final price reflects the 7 % discount. For headless verification, send a request to the Rule API:
POST /api/search/rule
{
"context": {
"currencyId": "5000000000",
"customerGroupId": "gold",
"lineItems": [
{ "productId": "123", "quantity": 1, "price": 120 }
]
}
}
The response should contain the rule ID that matches the created rule. Compare it with the rule ID shown in the Rule Builder UI to confirm alignment.
Trade‑offs and Limits
- Recursion depth. Deeply nested OR groups (>3 levels) can exceed PHP’s recursion limit, causing fatal errors. Keep the tree shallow.
- N+1 queries. Custom conditions that hit the database per rule iteration lead to performance degradation. Batch load data via
RuleScopeor pre‑fetch withRuleLoader. - Manual versioning. Without a draft workflow, accidental rule changes may affect live promotions. Use a separate draft rule entity and a Flow to promote on schedule.
- Sales channel immutability. Rules cannot switch currency or language mid‑evaluation. Create distinct rules per channel if needed.
- Elasticsearch visibility. Product visibility rules are not evaluated during Elasticsearch search; apply them post‑search or build a custom
SearchRankingBuilder.
Getting Started: Quick Checklist
- Identify the business logic you need (pricing, shipping, visibility).
- Model the condition using built‑in operators; for custom fields, register a
RuleConditionservice tagged withshopware.rule.condition. - Write the rule in the admin UI, keep nesting shallow, and test locally.
- Clear the cache after any rule change:
bin/console cache:clear. - Run a performance benchmark with the profiler: enable Shopware\Core\Framework\Adapter\Twig\Profiler, load a realistic cart, and inspect the
rule.evaluationtimeline. - For headless integrations, call
POST /api/search/rulewith a simulated context and verify returned rule IDs.
By following these steps, merchants can harness the Rule Builder’s no‑code power while keeping checkout speeds high and maintaining a clean, maintainable rule set.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.