FieldCraftDocsServicesBlogWork With Me →

Computed Fields

Auto-compute field values from other responses.

FieldCraft includes two field types that derive their values from other fields:calculated for math expressions and scoring for weighted option scores with color-coded ranges.

Calculated Fields

A calculated field evaluates a math expression that references other field values. It recalculates automatically whenever a dependency changes.

{
id: "bmi",
type: "calculated",
label: "BMI",
config: {
expression: "{weight} * 703 / ({height} ^ 2)",
format: "number",
decimalPlaces: 1,
},
}

Expression Syntax

Reference fields with {fieldId}. The engine replaces each reference with the field's numeric value and evaluates the result.

FeatureSyntaxExample
Field reference{fieldId}{price}
Arithmetic+ - * /{price} * {quantity}
Power^{height} ^ 2
Parentheses( )({a} + {b}) / 2
Functionsfloor ceil round abs min maxround({total} * 100) / 100

Expressions are parsed with a sandboxed Shunting-yard evaluator — no eval()is used. If any referenced field is missing or non-numeric, the result is null.

Config Options

PropertyTypeDescription
expressionstringMath expression with field references
format"number" | "currency" | "percentage"Display format
decimalPlacesnumberRounding precision
prefixstringDisplay prefix (e.g., "$")
suffixstringDisplay suffix (e.g., "kg")
visiblebooleanWhether to render the field (default true)

Example: Order Total

const schema: FormEngineSchema = {
id: "order",
version: "1.0.0",
title: "Order Form",
submitAction: { type: "callback" },
sections: [{
id: "items",
title: "Order",
questions: [
{ id: "price", type: "number", label: "Unit Price", required: true },
{ id: "quantity", type: "number", label: "Quantity", required: true },
{
id: "subtotal",
type: "calculated",
label: "Subtotal",
config: {
expression: "{price} * {quantity}",
format: "currency",
prefix: "$",
decimalPlaces: 2,
},
},
{
id: "tax",
type: "calculated",
label: "Tax (8%)",
config: {
expression: "{price} * {quantity} * 0.08",
format: "currency",
prefix: "$",
decimalPlaces: 2,
},
},
{
id: "total",
type: "calculated",
label: "Total",
config: {
expression: "{subtotal} + {tax}",
format: "currency",
prefix: "$",
decimalPlaces: 2,
},
},
],
}],
};

Scoring Fields

A scoring field maps selected options to numeric scores and displays a total with configurable color ranges.

{
id: "risk_score",
type: "scoring",
label: "Risk Assessment",
config: {
options: [
{ label: "Low", value: "low", score: 1 },
{ label: "Medium", value: "medium", score: 5 },
{ label: "High", value: "high", score: 10 },
],
showScore: true,
scoreRanges: [
{ min: 0, max: 3, label: "Low Risk", color: "#22c55e" },
{ min: 4, max: 7, label: "Moderate Risk", color: "#eab308" },
{ min: 8, max: 10, label: "High Risk", color: "#ef4444", description: "Requires review" },
],
},
}

Score Range Properties

PropertyTypeDescription
minnumberRange lower bound (inclusive)
maxnumberRange upper bound (inclusive)
labelstringDisplay label for the range
colorstringCSS color for the indicator
descriptionstringOptional description shown in the range

Accessing Computed Values

Computed values are stored in FormState.values like any other field. Scores are additionally tracked in FormState.scores:

const state = engine.getState();

// Calculated field value
const bmi = state.values["bmi"]; // number | null

// Scoring field values
const scores = state.scores; // Record<string, number>
const total = state.totalScore; // number (sum of all scoring fields)

Dependency Graph

The engine builds a dependency graph at initialization by parsing field references in expressions. When a field value changes, only its dependents are recalculated — not the entire form. Chained dependencies (A → B → C) resolve in the correct order.