Home / Blog / Evaluating FEEL expressions in TypeScript

Evaluating FEEL expressions in TypeScript

FEEL (Friendly Enough Expression Language) is the expression language DMN 1.3 defines for decision tables, and it’s what Camunda 8 uses for BPMN gateway conditions and variable mappings too. If you’re generating processes or decision tables programmatically, being able to parse and evaluate FEEL yourself — outside of a running Camunda engine — is useful for validation, testing, and tooling.

@bpmnkit/feel is a zero-dependency FEEL parser and evaluator with 87 built-in functions implemented.

Parsing and evaluating an expression

FEEL evaluation is two steps: parse the expression into an AST, then evaluate that AST against a variable context.

import { parseExpression, evaluate } from "@bpmnkit/feel";

const { ast, errors } = parseExpression("amount > 1000 and status = \"pending\"");
if (errors.length > 0) {
  throw new Error(`Invalid FEEL expression: ${errors[0].message}`);
}

const result = evaluate(ast, {
  vars: { amount: 1500, status: "pending" },
});
// result === true

Separating parse from evaluate matters for tooling: you can parse once, check for syntax errors immediately (useful for validating a gateway condition before it’s ever run), and evaluate many times against different variable contexts — exactly what a process simulation does for every token that reaches that gateway.

What’s actually supported

The evaluator handles the FEEL data model beyond plain booleans and numbers: dates, times, durations, ranges, lists, and contexts, plus the string/list/date functions documented on the function reference page — things like:

const { ast } = parseExpression('date("2026-07-07") + duration("P30D")');
evaluate(ast, { vars: {} });
// → a FEEL date 30 days after 2026-07-07

Where this shows up in BPMN Kit

Gateway conditions built with @bpmnkit/core’s fluent API are plain FEEL strings:

.branch("approved", (b) =>
  b.condition("= amount <= 5000")
    .serviceTask("auto-approve", { taskType: "auto-approve" })
    .endEvent("end-approved"),
)

@bpmnkit/engine’s simulation engine uses the same @bpmnkit/feel evaluator internally to decide which branch a token takes — so testing a condition with parseExpression/evaluate directly exercises the identical logic a real simulation run would use.

Where to go next