back to docs
[reference] formulas calculated-fields expressions logic

Formulas

Formula engine reference: calculated fields, validation rules, conditional logic, navigation paths, and built-in functions.

published · updated

Overview

Formulas use a small, deterministic DSL (not JavaScript, Python, or SQL). The grammar is deliberately simple, safe, and side-effect free. Formulas are parsed once on the server into an AST, cached, and re-evaluated at runtime — sometimes on the server, sometimes on the client, sometimes inlined into the SQL query.

They appear in:

  • Calculated columns — formula columns (columnType: "F") that compute values from other fields
  • Generated columns — physical columns (columnType: "G") auto-maintained by the database, used for aggregates over child sets (e.g., SUM([lines].[amount]))
  • Default values — set initial values on new records
  • Validation rulesCHECK constraints that enforce business logic
  • Action conditionscanExecute formulas that control when actions are available
  • Row-level security — filter expressions that restrict which records a user can see
  • Display stringstoString templates with {field} placeholders

Syntax

Field References

Access the current record’s fields with brackets:

[quantity]
[unit_price]
[account_name]

Arithmetic

[quantity] * [unit_price]
[subtotal] + [tax_amount]
[total] - [discount]
[amount] / [count]

String Operations

[first_name] + ' ' + [last_name]

Comparison

dForge uses SQL-style single equals for equality, not ==.

[amount] > 1000
[status] = 'Active'
[end_date] >= [start_date]
[category] != 'Internal'

Other comparison operators: <, <=, >, >=, IN, NOT IN, BETWEEN.

Logical Operators

[amount] > 1000 AND [status] == 'Approved'
[type] == 'A' OR [type] == 'B'
NOT [is_archived]

Conditional Logic

IF([amount] > 10000, 'Enterprise', IF([amount] > 1000, 'Business', 'Standard'))
IF([discount_pct] > 0, [price] * (1 - [discount_pct] / 100), [price])

IN Operator

[status] IN ('Open', 'In Progress', 'Pending')
[type] NOT IN ('Internal', 'Test')
[amount] BETWEEN 100 AND 1000

String Predicates

[name] CONTAINS 'corp'
[email] STARTS_WITH 'admin@'
[file] ENDS_WITH '.pdf'

Follow references to access fields from related entities. Navigate through the reference virtual column (not the FK column itself):

[customer].[name]
[customer].[industry]
[order].[customer].[email]

Navigation formulas are async — they require a separate data fetch. The platform detects navigation patterns automatically and resolves them after sync formulas. The frontend caches each referenced entity to avoid N+1 fetches.

How It Works

  1. [customer] — the reference column on the current record
  2. . — follows the reference to the target entity
  3. [name] — reads the field from the referenced record

Multi-level navigation is supported: [order].[customer].[name] follows two references. Currently the client supports up to 2 levels.

Sync vs Async Formulas

TypeWhen evaluatedExample
SyncImmediately on data load and field change[quantity] * [unit_price]
AsyncAfter sync formulas, requires data fetch[account_id].[industry]

The platform automatically classifies formulas based on whether they contain navigation patterns ([field].[field]).

Dependency Tracking

Formulas declare their dependencies through field references. When a field changes:

  1. All sync formulas that depend on it are re-evaluated immediately
  2. All async formulas that depend on it trigger a data fetch and re-evaluate when the data arrives

Formulas are topologically sorted by dependencies to ensure correct evaluation order.

Built-in Functions

Date Functions

FunctionDescriptionExample
TODAY()Current date[due_date] >= TODAY()
NOW()Current date/time[created_at] > NOW()
WEEKDAY(date)Day of week (1-7)WEEKDAY(TODAY())
STARTMONTH(date)First day of the date’s monthSTARTMONTH(NOW())
ENDMONTH(date)Last day of the date’s monthENDMONTH(NOW())
STARTQUARTER(date)First day of the date’s quarterSTARTQUARTER(NOW())
ENDQUARTER(date)Last day of the date’s quarterENDQUARTER(NOW())
STARTYEAR(date)First day of the date’s yearSTARTYEAR(NOW())

Context Functions

ExpressionDescription
CURRENT_USER_ID()The ID of the current user
$[SettingName]Value of a module setting (resolved from folder context — see below)

Conditional Functions

FunctionDescriptionExample
IF(cond, then, else)Conditional valueIF([qty] > 100, 'Bulk', 'Standard')

Aggregate Functions (Generated Columns)

Aggregate functions operate on a set field (1:N child collection) and require columnType: "G" — a physical column auto-maintained by the database.

FunctionDescription
SUM([set].[field])Sum of field values across child records
COUNT([set].[field])Count of child records
AVG([set].[field])Average value
MIN([set].[field])Minimum value
MAX([set].[field])Maximum value

Multi-column expressions inside the aggregate are supported:

SUM([lines].[quantity] * [lines].[unit_price])

The platform automatically picks the right strategy: same-row formulas become PostgreSQL GENERATED ALWAYS AS ... STORED columns, while set aggregates use database triggers on the child table.

Settings References

Module settings are configurable values that can be overridden per folder. Reference them in formulas with $[SettingName]:

[department_id] = $[DepartmentId]               -- row-level security
[amount] <= $[ApprovalThreshold]                -- canExecute condition
$[DefaultWarehouseId]                            -- default value

When a formula references a setting, the platform looks it up in the current folder, then walks up the folder tree, and finally falls back to the module default. Returns NULL if no value is set anywhere.

Usage Examples

Calculated Total

[quantity] * [unit_price] * (1 - [discount_pct] / 100)

A formula column on an invoice line that computes the line total after discount.

Full Name

[first_name] + ' ' + [last_name]

A formula column that combines name parts for display.

Overdue Check

[due_date] < TODAY() AND [status] != 'Paid'

Used as a check constraint or conditional formatting rule.

Stage-Based Amount Forecast

IF([stage] == 'Closed Won', [amount],
IF([stage] == 'Negotiation', [amount] * 0.7,
IF([stage] == 'Proposal', [amount] * 0.4,
[amount] * 0.1)))

A weighted pipeline forecast based on opportunity stage.

Cross-Entity Display

[customer].[name] + ' (' + [customer].[industry] + ')'

An async formula that shows customer name and industry from the linked customer record.

toString Templates

The toString property on an entity uses a different syntax — {field} placeholders inside a text template, not bracket expressions:

"toString": "{invoice_number} — {customer}"
"toString": "{first_name} {last_name}"
"toString": "{product} x {quantity}"

When a placeholder names a reference field (e.g., {customer}), the platform recursively resolves it using the referenced entity’s own toString. This is evaluated at SQL time (via JOINs in a single query), avoiding N+1 fetches.

Formula Columns in Entity Definitions

When defining a formula column (column type F), you must specify:

PropertyRequiredDescription
formulaYesThe expression text
baseDatatypeCdYesResult data type: string, number, date, bool
fieldTypeCdYesHow to display: text, number, currency, date, etc.
columnTypeYesMust be "F"
flagsYesUsually "V" (visible) — formula columns are never editable

Check Constraints

Entities can define check constraints using formula syntax:

"constraints": {
  "positive_quantity": {
    "type": "check",
    "expression": "quantity > 0",
    "message": "Quantity must be positive"
  },
  "valid_date_range": {
    "type": "check",
    "expression": "end_date >= start_date",
    "message": "End date must be on or after start date"
  }
}

Check constraints become real PostgreSQL CHECK constraints (server-side enforcement) and are also evaluated client-side via the formula evaluator for immediate UI feedback. If the expression returns false, the save is rejected with the specified error message.

Where Formulas Run

ContextEvaluated onWhy
toString displayServer / SQL timeJOINs to referenced tables in one query — no N+1
canExecuteClient + ServerClient for instant button state; server re-checks for security
Simple calculated fieldsClientImmediate feedback when the user edits a field
Default valuesServerApplied on insert
Row-level securityServerEnforced at query time
Validation / CHECKServer (and client for UI feedback)Enforced on save
Generated column aggregatesDatabasePostgreSQL trigger or GENERATED ALWAYS AS column
/ was this helpful?

Stuck on something?
Tell us.

We read every message and update the docs based on what readers ask. The fastest way to improve the docs is to write to us.