---
title: "Writing formulas with Handlebars"
description: "How formulas work in Pave"
canonical_url: "https://support.pave.com/articles/writing-formulas-with-handlebars-UY3vIXn2gv"
md_url: "https://support.pave.com/articles/writing-formulas-with-handlebars-UY3vIXn2gv.md"
---
# Writing formulas with Handlebars

## How formulas work in Pave

Formulas let you create calculated columns that derive values from other data in the cycle. You write them using a templating language called Handlebars. If you've written spreadsheet formulas, the concepts are the same: reference a cell (here, a column), apply a function, get a result.

Every formula goes inside double curly braces: `{{ }}`. Inside the braces, you reference employee data with `columns.fieldName` and call built-in functions (called helpers) by name.

### Your first formula
To output an employee's base pay, just reference it:
```
{{columns.basePay}}
```

To multiply base pay by 5%:
```
{{multiply columns.basePay 0.05}}
```

To nest one function inside another, wrap the inner one in parentheses:
```
{{round (divide columns.annualBonusCash columns.basePay) 2}}
```
This divides bonus by base pay, then rounds to 2 decimal places.

That's the entire syntax. The rest of this guide covers what helpers are available and how to combine them.

## If/else: choosing between values

This is the most commonly used pattern. Use `#if` to branch based on a condition.

**Simple if/else:**
```
{{#if columns.isPromotion}}
  {{multiply columns.newBase 0.10}}
{{else}}
  0
{{/if}}
```

**Multiple branches (if / else if):**
```
{{#if (eq columns.level "P1")}}0.10
{{else if (eq columns.level "P2")}}0.15
{{else if (eq columns.level "P3")}}0.20
{{else}}0
{{/if}}
```

**Inline if (one-line, inside another expression):**
```
{{add columns.salary (inlineIf columns.eligible columns.bonus 0)}}
```

**Unless (opposite of if):**
```
{{#unless columns.isExcluded}}
  {{columns.salaryRaise}}
{{/unless}}
```

Every `{{#if}}` must end with `{{/if}}`. Every `{{#unless}}` must end with `{{/unless}}`.

## Comparison helpers

Use these inside `#if` to compare values. They return true or false.

| Helper | Meaning | Example |
|---|---|---|
| `eq` | Equals | `(eq columns.country "Japan")` |
| `ne` | Not equals | `(ne columns.variablePay 0)` |
| `gt` | Greater than | `(gt columns.tenureDays 1095)` |
| `gte` | Greater than or equal | `(gte columns.daysInRole 365)` |
| `lt` | Less than | `(lt columns.startDate '2025-01-01')` |
| `lte` | Less than or equal | `(lte columns.compaRatio 1.0)` |

## Logical helpers

Combine multiple conditions.

| Helper | Meaning | Example |
|---|---|---|
| `and` | True if all are true | `(and (eq columns.dept "Sales") (gt columns.tenure 1))` |
| `or` | True if any are true | `(or (eq columns.level "P1") (eq columns.level "P2"))` |
| `not` | Flips true to false | `(not columns.isPromotion)` |
| `includes` | True if text/list contains a value | `(includes columns.tags "Sales")` |
| `isNullOrUndefined` | True if the field is empty | `(isNullOrUndefined columns.bonusOverride)` |

## Math helpers


| Helper | What it does | Example |
|---|---|---|
| `add` | Adds numbers | `{{add columns.basePay columns.variablePay}}` |
| `subtract` | Subtracts | `{{subtract columns.newBase columns.basePay}}` |
| `multiply` | Multiplies | `{{multiply columns.basePay 0.05}}` |
| `divide` | Divides | `{{divide columns.basePay 12}}` |
| `round` | Rounds to N decimals | `{{round columns.value 2}}` |
| `ceil` | Rounds up | `{{ceil (divide columns.shares 100)}}` |
| `floor` | Rounds down | `{{floor (divide columns.shares 100)}}` |
| `max` | Largest of the values | `{{max columns.a columns.b}}` |
| `min` | Smallest of the values | `{{min columns.a columns.b}}` |
| `abs` | Absolute value | `{{abs (subtract columns.a columns.b)}}` |
| `mod` | Remainder after division | `{{mod columns.x 2}}` |
| `power` | Raises to a power | `{{power columns.x 2}}` |

### Rounding to specific increments
The `round` helper rounds to decimal places. To round to the nearest $100 or $1,000, combine `round` with `multiply` and `divide`:

```
Nearest $100:    {{multiply (round (divide columns.newBase 100)) 100}}
Nearest $1,000:  {{multiply (round (divide columns.newBase 1000)) 1000}}
Nearest cent:    {{divide (round (multiply columns.newBase 100)) 100}}
```

## Date helpers


| Helper | What it does | Example |
|---|---|---|
| `dayDiff` | Days between two dates | `{{dayDiff '2025-01-01' columns.startDate}}` |
| `monthDiff` | Full months between two dates | `{{monthDiff columns.startDate '2026-02-01'}}` |
| `duration` | Human-readable tenure | `{{duration columns.startDate '2026-02-01'}}` |
| `dateAdd` | Add days to a date | `{{dateAdd columns.startDate 90 'days'}}` |
| `dateFormat` | Format a date for display | `{{dateFormat columns.startDate 'MM/dd/yyyy'}}` |

## Text helpers


| Helper | What it does | Example |
|---|---|---|
| `concat` | Joins strings | `{{concat columns.firstName " " columns.lastName}}` |
| `split` | Splits text; optional index returns one piece | `{{split columns.fullName ' ' 0}}` (first name) |
| `substring` | Extracts part of a string | `{{substring columns.level 0 1}}` (first character) |
| `replace` | Replaces text | `{{replace columns.salary ',' ''}}` (strip commas) |
| `formatCurrency` | Formats a number as currency | `{{formatCurrency columns.basePay 'USD'}}` |
| `toNumber` | Converts text to a number | `{{toNumber columns.salaryFromUpload}}` |
| `toString` | Converts a number to text | `{{toString columns.level}}` |
| `toUpper` | Uppercases text | `{{toUpper columns.country}}` |
| `toLower` | Lowercases text | `{{toLower columns.email}}` |
| `titleCase` | Capitalizes each word | `{{titleCase columns.name}}` |

## Handling empty values


| Helper | What it does | Example |
|---|---|---|
| `coalesce` | Returns the first non-empty value | `{{coalesce columns.override columns.default}}` |
| `isNullOrUndefined` | True if a field is empty (use inside `#if`) | `{{#if (not (isNullOrUndefined columns.bonus))}}...{{/if}}` |

## Saving intermediate values with setVar


When your formula needs the same value in multiple places, or when a calculation has several steps that are hard to follow as a single nested expression, `setVar` lets you break it into named pieces. You assign an intermediate result to a name, then reference that name later in the formula as if it were a column. This is especially useful for multi-step calculations like proration (compute daily pay, compute days worked, then multiply), formulas that reuse the same sub-expression (compute a base amount once, then use it in both a percentage and a dollar calculation), and lookup-driven logic where you store a converted value (like an FX-adjusted salary) before applying further math to it. Without `setVar`, these formulas would require deeply nested expressions that are difficult to read and debug.

For long calculations, save partial results into a named variable to keep things readable.

```
{{setVar 'dailyPay' (divide columns.basePay 365)}}
{{setVar 'daysWorked' (dayDiff columns.startDate '2025-12-31')}}
{{multiply dailyPay daysWorked}}
```

## Working with lookup tables (setMapVar / getMapVal)


Some fields contain structured data (like band values with min/target/max). Use `getMapVal` to pull one value out.

```
{{getMapVal columns.equityBandRawValues 'target'}}
```

You can also create your own lookup tables inline. This is commonly used for FX rates or level-to-tier mappings:

```
{{setMapVar 'fxRates' '{"USD":1,"EUR":1.08,"GBP":1.27}'}}
{{multiply columns.basePay (getMapVal fxRates columns.currency)}}
```

## Controlling whitespace


Formulas that span multiple lines can produce unexpected whitespace in the output. Add `~` inside the braces to strip whitespace from that side:

```
{{#if columns.isPromotion}}
  {{~columns.newBase~}}
{{else}}
  {{~columns.basePay~}}
{{/if}}
```

The `~` strips whitespace from whichever side it's on. `{{~value~}}` strips both sides. `{{value~}}` strips only the right side.

## Common recipes

These are the most-used patterns across Pave customers.

**On-target earnings:**
```
{{add columns.basePay columns.variablePay}}
```

**Percent change between current and new total cash:**
```
{{divide (subtract columns.newTotalAnnualCash columns.currentTotalAnnualCash) columns.currentTotalAnnualCash}}
```

**Pro-rated bonus by start date:**
```
{{multiply columns.annualBonusCash (divide (dayDiff columns.startDate '2025-12-31') 365)}}
```

**Cap a raise at zero (no negative raises):**
```
{{max (subtract columns.newBase columns.basePay) 0}}
```

**Compa-ratio:**
```
{{divide columns.basePay (getMapVal columns.salaryImpact 'bandMid')}}
```

**Monthly salary from annual:**
```
{{divide columns.basePay 12}}
```

**Hourly rate (2,080 working hours/year):**
```
{{divide columns.basePay 2080}}
```

**First name (for reward letters):**
```
{{split columns.fullName ' ' 0}}
```

**Multi-step calculation:**
```
{{setVar 'increase' (multiply columns.basePay 0.025)}}
{{setVar 'days' (dayDiff columns.startDate columns.effectiveDate)}}
{{divide (multiply increase days) 365}}
```

## Tips

- Use the **live preview** in the column editor to test your formula against real employee data before saving.
- Strings need quotes (`"Sales"` or `'Sales'`). Numbers don't.
- Whitespace and line breaks inside `{{ }}` don't affect the result. Use them to keep complex formulas readable.
- Combine operations by nesting with parentheses: `{{multiply (add a b) c}}`.
