> ## Documentation Index
> Fetch the complete documentation index at: https://docs.getveles.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Formula Rules

> Build complex pricing and validation logic using the Veles formula engine: variables, control flow, list processing, and Data Sheet lookups.

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](/get-started/pricing-and-packaging/data-sheets). If you can express it as a formula, you can build it as a rule.

<Info>
  Formula Rules are a power-user feature. For straightforward guardrails (discount caps, required products, price floors), [Quote Validation](/get-started/rules-and-approval/quote-validation-rules), [Row Validation](/get-started/rules-and-approval/untitled-page), and [Pricing Rules](/get-started/rules-and-approval/pricing-rules) are simpler to configure. Use Formula Rules when those standard kinds can't express the logic you need.
</Info>

***

## 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

| Type          | Syntax                                       | Examples                                    |
| ------------- | -------------------------------------------- | ------------------------------------------- |
| **Numbers**   | Integers, decimals, scientific notation, hex | `100`, `15.5`, `1e-3`, `0x1A`               |
| **Strings**   | Single or double quotes                      | `'Enterprise'`, `"USD"`                     |
| **Booleans**  | Lowercase keywords                           | `true`, `false`                             |
| **Null**      | Keyword                                      | `null`                                      |
| **Arrays**    | Braces                                       | `{1, 2, 3}`                                 |
| **Dates**     | DD-MM-YYYY format                            | `22-01-2026`                                |
| **Durations** | Duration keyword                             | `duration(5, days)`, `duration(12, months)` |

### Operators

Evaluated in order of precedence (highest first):

| Category           | Operators                             | Description                                                |    |         |
| ------------------ | ------------------------------------- | ---------------------------------------------------------- | -- | ------- |
| **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

| Variable           | Type   | Description                                   |
| ------------------ | ------ | --------------------------------------------- |
| `quote.segment`    | String | The segment name (e.g., "Enterprise", "SMB"). |
| `quote.segment_id` | String | The unique UUID for the segment.              |

### Option-level variables

| Variable                 | Type    | Description                                                       |
| ------------------------ | ------- | ----------------------------------------------------------------- |
| `option.frequency`       | Integer | Billing cycles per year. 1 = annual, 4 = quarterly, 12 = monthly. |
| `option.currency_code`   | String  | 3-letter currency code (e.g., "USD", "EUR").                      |
| `option.term_length`     | Integer | Contract length in months.                                        |
| `option.product_skus`    | List    | Array of all SKUs in the option.                                  |
| `option.promotion_count` | Integer | Number of promotions applied.                                     |

### Phase-level variables

| Variable           | Type    | Description                                                              |
| ------------------ | ------- | ------------------------------------------------------------------------ |
| `phase.total`      | Numeric | Net price after discounts (annualized).                                  |
| `phase.discount`   | Numeric | Overall discount percentage (0-100).                                     |
| `phase.list_price` | Numeric | Total price before discounts.                                            |
| `phase.year`       | Integer | Current year in a multi-year deal (1-indexed).                           |
| `phase.lines`      | List    | Array of line item objects. Used for advanced filtering and aggregation. |

***

## Control flow

### IF function

For simple true/false branching:

```text theme={null}
if(condition, true_value, false_value)
```

**Example:** Return a different discount cap based on billing frequency:

```text theme={null}
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:

```text theme={null}
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:

```text theme={null}
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:

```text theme={null}
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:

```text theme={null}
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:

```text theme={null}
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](/get-started/pricing-and-packaging/data-sheets) from within a formula:

```text theme={null}
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:

```text theme={null}
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:

```text theme={null}
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

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

### String manipulation

| Function                     | Usage                                             | Description                         |
| ---------------------------- | ------------------------------------------------- | ----------------------------------- |
| `concat(a, b, ...)`          | `concat("Hello", " ", "World")` → `"Hello World"` | Join strings.                       |
| `contains(text, search)`     | `contains("Enterprise", "Enter")` → `true`        | Check 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

| Function            | Usage                  | Description                       |
| ------------------- | ---------------------- | --------------------------------- |
| `duration(n, unit)` | `duration(12, months)` | Create a duration for comparison. |

**Example:** Check if the contract term meets a minimum threshold:

```text theme={null}
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:

```text theme={null}
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:

```text theme={null}
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:

```text theme={null}
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):

```text theme={null}
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](/get-started/rules-and-approval/quote-validation-rules) or [Row Validation](/get-started/rules-and-approval/untitled-page) 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](/get-started/pricing-and-packaging/data-sheets) 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

<CardGroup cols={2}>
  <Card title="Data Sheets" icon="table" href="/get-started/pricing-and-packaging/data-sheets">
    Build the reference tables that VLOOKUP queries against.
  </Card>

  <Card title="Getting Started with Rules" icon="shield-check" href="/get-started/rules-and-approval/learn-the-basics">
    Overview of the rules engine and simpler rule kinds.
  </Card>

  <Card title="Pricing Rules" icon="tag" href="/get-started/rules-and-approval/pricing-rules">
    Automatic price adjustments without formula syntax.
  </Card>

  <Card title="Translating SFDC Rules" icon="arrow-right-arrow-left" href="/get-started/rules-and-approval/translating-sfdc-rules-to-veles">
    Map Salesforce CPQ rules to the Veles formula engine.
  </Card>
</CardGroup>
