OmniStudio for Energy & Utilities: Architecture Patterns and Lessons Learned
Energy & Utilities is one of the most OmniStudio-intensive verticals in the Salesforce ecosystem. Complex pricing models, multi-year contracts, regulatory approval chains, and modulation curves push OmniStudio components well beyond what a typical guided-flow implementation requires. After working on large-scale E&U projects with OmniStudio and Salesforce CPQ, here are the architecture patterns that actually work in production, and the lessons I wish I had learned earlier.
Why E&U projects are different
In a typical OmniStudio implementation (insurance quoting, telecom onboarding), the OmniScript collects user input, an Integration Procedure processes it, and a DataMapper writes to Salesforce. The data volume per transaction is moderate and the calculation logic is straightforward.
Energy projects break this model. A single pricing calculation might involve hundreds of annual/monthly records across multiple price curves, discount tiers, certificate values, and regulatory spreads. The Data JSON in an OmniScript can easily exceed 500KB. Integration Procedures chain into Apex callable classes that run batch calculations. Approval processes depend on computed values that only exist after the pricing engine finishes its async cycle.
This changes everything about how you architect your OmniStudio solution.
Pattern 1: The async pricing engine
The most critical pattern in E&U is decoupling the pricing calculation from the OmniScript. Instead of running all pricing logic inside an Integration Procedure (which would hit governor limits), the architecture splits into layers:
Layer 1: OmniScript (UI). Collects user selections: product type, contract period, price list, modulation parameters. The OmniScript submits a lightweight JSON payload to the backend via an IP Action.
Layer 2: Integration Procedure (orchestration). Validates inputs, runs pre-checks (e.g., does this Quote Line Item exist? Is the selected matrix valid?), and then invokes an Apex callable class.
Layer 3: Apex callable class (engine). This is where the heavy lifting happens. The callable class implements the Callable interface and typically follows this chain:
call() → invokeMethod('processEnergyData')
→ new ProcessEnergyData(inputMap)
→ setQuoteAndUpdate()
→ persistCalculationData()
The engine reads the Data JSON (which contains everything from the OmniScript: opportunity data, configuration flags, discount curves, account info), calculates annual and monthly energy values, applies discount tiers, injects certificate values, and writes results back to Quote Line Items and custom objects.
Layer 4: Async notification. Because the calculation can take several seconds, the OmniScript uses a PubSub LWC component to listen for a Platform Event that signals completion. The UI shows a spinner until the event fires, then refreshes the data. A "noValidId" guard on the processId setter prevents premature rendering.
Why not run everything in the IP?
Governor limits. A complex pricing calculation touching 200+ records, running discount formulas, and doing DML across multiple objects will easily exceed the CPU time limit inside an Integration Procedure. The Apex callable pattern gives you full control over bulkification, queueable chaining, and selective DML.
Pattern 2: OmniScript-driven CPQ with approval workflows
In E&U, the quoting process is not just "pick a product and generate a price." It involves:
Selecting a price list from a catalog (which itself might be blank due to CPQ configuration issues). Configuring contract parameters (duration, modulation type, certificate eligibility). Running the pricing engine (Pattern 1). Submitting for multi-level approval based on computed spreads and discount thresholds.
The OmniScript acts as the orchestrator for this entire flow. Each Step corresponds to a phase: product selection, configuration, pricing review, approval submission. Between steps, IP Actions and Set Values elements prepare and validate data.
A critical architectural decision: where do approval criteria live? In many E&U projects, the approval logic depends on calculated values (like a "Global Spread" that consolidates multiple discount curves into a single metric). These values only exist after the pricing engine runs. The approval criteria must read from the persisted calculation results, not from the OmniScript Data JSON, because the Data JSON is a client-side snapshot that can be stale.
The Global Spread pattern
Instead of evaluating approval conditions on individual line-level spreads, consolidate them into a single SpreadGlobalOriginal (before discounts) and SpreadGlobalAtual (after discounts) stored on the Quote or a custom object. The approval process entry criteria reference these two fields. This avoids the combinatorial explosion of checking every line item individually and makes the approval logic auditable.
Pattern 3: Data JSON size management
In E&U OmniScripts, the Data JSON grows fast. A typical structure includes:
ResponseOpportunity
ResponseConfigProductStep1
└─ BLC_Config
└─ BLC_Flags
└─ BlockSelectPriceList
ResponseDescontoGarantido
ResponseCurvaDescontos
ResponseAccount
quoteLineItem
QuoteId
When the pricing engine returns results, the response nodes add even more data. If you are not careful, the Data JSON can exceed 1MB, which causes performance degradation on the OmniScript (slow step transitions, laggy Conditional Views).
Lesson learned: trim Integration Procedure responses aggressively. In the IP's Response Action, map only the fields the OmniScript actually needs for display or downstream logic. Do not pass through entire SObject records. Use the "Response JSON Path" property to flatten nested structures.
Pattern 4: Conditional View discipline
OmniScript Conditional Views in E&U projects tend to multiply because the UI changes based on product type, contract phase, and user role. Three critical rules from production experience:
Always use the colon separator in field paths: StepName:ElementName, never dot notation. This is the single most common cause of "my Conditional View doesn't work."
Boolean vs. string mismatch. If a checkbox element returns true (boolean) but your Conditional View compares against "true" (string), it will silently fail. Always test with {Data} open in the preview to verify the actual type in the JSON.
Minimize the number of Conditional Views. Each one adds evaluation overhead on every step transition. In a 15-step OmniScript with 30+ Conditional Views, the cumulative cost is noticeable. Consolidate where possible: instead of 5 Conditional Views toggling 5 elements, use a single Conditional View on a Block that contains all 5.
Pattern 5: Pre-validation before heavy computation
Do not let the pricing engine run on invalid data. Build a pre-validation layer in the Integration Procedure that checks all preconditions before invoking the Apex callable:
// In the IP, before calling the engine:
validateUcsForCativoMatrix → checks if the selected
Unit Consumer has valid matrix entries
validatePriceListSelection → checks if the product
catalog returned a valid price list
validateContractDates → normalizes DataInicial/DataFinal
to month boundaries
If validation fails, return a structured error code (e.g., noLinesDM for missing Decision Matrix entries) that the OmniScript can display in a Set Errors element. This is far better than letting the engine fail mid-calculation with an opaque Apex exception.
Key takeaways
Decouple the pricing engine from the OmniScript. Use Apex callable classes for heavy computation, Integration Procedures for orchestration, and PubSub for async UI updates.
Keep the Data JSON lean. Trim IP responses to only the fields the UI needs.
Centralize approval criteria on persisted computed fields (like Global Spread), not on the Data JSON.
Enforce colon separators, boolean type consistency, and minimal Conditional Views as non-negotiable coding standards.
Always pre-validate before running expensive calculations.
These patterns are not theoretical. They come from debugging production issues, refactoring approval flows that silently passed invalid quotes, and optimizing OmniScripts that took 8 seconds to transition between steps. If you are starting an E&U project with OmniStudio, build these patterns into your architecture from day one.
Need OmniStudio expertise for your Energy & Utilities project?
I'm a Senior OmniStudio Developer with 5 Salesforce certifications and hands-on E&U experience. Available for remote consulting, technical support, and contract work worldwide.
Work with meWant to learn OmniStudio from the ground up? Check out OmniStudio na Prática, the most complete OmniStudio course available, with 130+ lessons covering every component. Or follow the OmniStudio 365 project for daily practical posts.