Skip to main content
Formula Rules are the most flexible rule type in Veles. They use a custom expression language (similar to Excel or SQL) to evaluate conditions and calculate values that go beyond what the standard rule builder can express. Use Formula Rules when your logic involves multiple variables, conditional branching, list aggregation, or lookups against Data Sheets. If you can express it as a formula, you can build it as a rule.
Formula Rules are a power-user feature. For straightforward guardrails (discount caps, required products, price floors), Quote Validation, Row Validation, and Pricing Rules are simpler to configure. Use Formula Rules when those standard kinds can’t express the logic you need.

When to use Formula Rules

Formula Rules are the right choice when:
  • The condition involves multiple variables combined (e.g., TCV > $500k AND term < 24 months AND segment = Enterprise)
  • You need conditional branching based on segment, region, or product tier (CASE/IF logic)
  • The logic requires aggregating across line items (e.g., sum the price of all “Enterprise” tier products)
  • You need to look up values from a Data Sheet at evaluation time (VLOOKUP)
  • The calculation involves date or duration math (e.g., contract length thresholds)

Creating a Formula Rule

  1. Navigate to Admin Console > Price Books > Rules.
  2. Click Create New Rule.
  3. Set the Kind to Formula Validation.
  4. Enter a Name and Description.
  5. Set the Status to Active.
  6. Click Save, then open the formula editor to write your expression.
The formula editor provides syntax highlighting and validation. The formula must evaluate to a boolean (true/false) for validation rules, or a numeric value for pricing adjustments.

Language basics

Data types

TypeSyntaxExamples
NumbersIntegers, decimals, scientific notation, hex100, 15.5, 1e-3, 0x1A
StringsSingle or double quotes'Enterprise', "USD"
BooleansLowercase keywordstrue, false
NullKeywordnull
ArraysBraces{1, 2, 3}
DatesDD-MM-YYYY format22-01-2026
DurationsDuration keywordduration(5, days), duration(12, months)

Operators

Evaluated in order of precedence (highest first):
CategoryOperatorsDescription
Unary+, -, %Positive/negative signs and percentage (e.g., 50% = 0.5)
Power^Exponentiation
Multiplicative*, /Multiplication and division
Additive+, -Addition and subtraction
Relational<, <=, >, >=, =, !=, <>Comparisons (return true/false)
Logical&&, ``AND, OR
Use parentheses to override precedence when needed: (a + b) * c.

Variables

Variables reference real-time data from the quote being built. They’re organized by scope.

Quote-level variables

VariableTypeDescription
quote.segmentStringThe segment name (e.g., “Enterprise”, “SMB”).
quote.segment_idStringThe unique UUID for the segment.

Option-level variables

VariableTypeDescription
option.frequencyIntegerBilling cycles per year. 1 = annual, 4 = quarterly, 12 = monthly.
option.currency_codeString3-letter currency code (e.g., “USD”, “EUR”).
option.term_lengthIntegerContract length in months.
option.product_skusListArray of all SKUs in the option.
option.promotion_countIntegerNumber of promotions applied.

Phase-level variables

VariableTypeDescription
phase.totalNumericNet price after discounts (annualized).
phase.discountNumericOverall discount percentage (0-100).
phase.list_priceNumericTotal price before discounts.
phase.yearIntegerCurrent year in a multi-year deal (1-indexed).
phase.linesListArray of line item objects. Used for advanced filtering and aggregation.

Control flow

IF function

For simple true/false branching:
if(condition, true_value, false_value)
Example: Return a different discount cap based on billing frequency:
if(option.frequency = 1, 25, 15)
This returns 25 (%) for annual billing and 15 (%) for everything else.

CASE expression

For multi-branch logic, use CASE. It evaluates conditions sequentially and returns the first match:
CASE
  WHEN quote.segment = "Enterprise" THEN 0.20
  WHEN quote.segment = "Mid-Market" THEN 0.10
  ELSE 0.05
END
This returns the maximum allowed discount based on the customer’s segment.

List processing

One of the most powerful features of the formula engine is the ability to filter, transform, and aggregate lists of line items.

FILTER

Returns a subset of a list based on a condition:
FILTER(phase.lines, line, line.category = "Software")
This returns only the Software line items from the current Phase.

MAP

Transforms every item in a list:
MAP(phase.lines, item, item.price)
This extracts the price from each line item, producing a list of numbers.

SUM

Aggregates a list of numbers:
SUM({100, 200, 300})
Returns 600.

Chaining: FILTER + MAP + SUM

The real power comes from chaining these together. For example, summing the price of all Enterprise-tier products in a Phase:
SUM(
  MAP(
    FILTER(phase.lines, line, line.product.custom_attributes.tier = "enterprise"),
    item,
    item.price
  )
)
This filters to Enterprise-tier lines, extracts their prices, and sums the result.

Data Sheet lookups

Use VLOOKUP to query Data Sheets from within a formula:
vlookup(sheet_name, lookup_column, lookup_value, return_column)
Example: Look up the maximum discount for the customer’s segment from a Discount_Thresholds Data Sheet:
vlookup("Discount_Thresholds", "Segment", quote.segment, "Max_Discount")
If the Data Sheet has a row where Segment = “Enterprise” and Max_Discount = 25, this returns 25. You can then use the looked-up value in a condition:
phase.discount <= vlookup("Discount_Thresholds", "Segment", quote.segment, "Max_Discount")
This formula evaluates to true when the discount is within the segment’s allowed maximum, and false when it exceeds it.

Function library

Math and logic

FunctionUsageDescription
abs(n)abs(-42)42Absolute value.
round(n, digits)round(3.456, 2)3.46Round to decimal places.
sqrt(n)sqrt(16)4Square root.
min(a, b, ...)min(10, 20, 5)5Minimum value.
max(a, b, ...)max(10, 20, 5)20Maximum value.
avg(list)avg({10, 20, 30})20Average of a list.

String manipulation

FunctionUsageDescription
concat(a, b, ...)concat("Hello", " ", "World")"Hello World"Join strings.
contains(text, search)contains("Enterprise", "Enter")trueCheck if text contains a substring.
left(text, n)left("Enterprise", 3)"Ent"Extract from the left.
right(text, n)right("Enterprise", 5)"prise"Extract from the right.
substitute(text, old, new)substitute("Hello World", "World", "Veles")Replace text.

Date and duration

FunctionUsageDescription
duration(n, unit)duration(12, months)Create a duration for comparison.
Example: Check if the contract term meets a minimum threshold:
option.term_length >= duration(12, months)

End-to-end examples

Segment-based discount validation

Validate that the discount doesn’t exceed the maximum for the customer’s segment, using a Data Sheet lookup:
phase.discount <= vlookup("Discount_Thresholds", "Segment", quote.segment, "Max_Discount")
  • Action if false: Warn, “Discount exceeds the maximum for this segment. Requires approval.”
  • Approval: Tier 2: Deal Desk

Multi-variable deal gate

Require VP approval when a deal has high value, short term, and deep discount:
NOT (phase.total > 500000 && option.term_length < 24 && phase.discount > 15)
  • Action if false: Warn, “Large short-term deal with significant discount. Requires VP review.”
  • Approval: Tier 3: VP of Sales

Year-over-year ramp validation

In a multi-year deal, ensure Year 2 pricing is not lower than Year 1:
if(phase.year = 1, true, phase.total >= phase.list_price * 0.95)
  • Action if false: Error, “Year 2+ pricing cannot be more than 5% below list price.”

Enterprise product concentration

Warn when Enterprise-tier products represent more than 80% of the deal value (over-concentration risk):
SUM(MAP(FILTER(phase.lines, line, line.product.custom_attributes.tier = "enterprise"), item, item.price)) / phase.total <= 0.80
  • Action if false: Warn, “Enterprise products represent more than 80% of deal value. Review product mix.”

Tips

Start with validation rules, graduate to formulas. If you can express the logic with the standard Quote Validation or Row Validation builder, do that. Formula Rules are more powerful but harder to debug and maintain. Use them when the standard builder genuinely can’t handle the logic. Use VLOOKUP instead of hard-coding. Any time you’re tempted to put a specific number in a formula (a threshold, a rate, a cap), consider putting it in a Data Sheet and looking it up with VLOOKUP. This makes the formula reusable across segments and easier to update. Test with edge cases. Formulas can produce unexpected results with null values, empty lists, or zero-division scenarios. Test with quotes that have one line item, zero discount, and unusual product combinations. Document your formulas. Use the rule’s Description field to explain the business logic in plain language. A formula like SUM(MAP(FILTER(...))) is opaque without context. The description should say what it does in business terms.

What’s next

Data Sheets

Build the reference tables that VLOOKUP queries against.

Getting Started with Rules

Overview of the rules engine and simpler rule kinds.

Pricing Rules

Automatic price adjustments without formula syntax.

Translating SFDC Rules

Map Salesforce CPQ rules to the Veles formula engine.