Skip to content

Resolvers

Resolvers are how Diagonal turns static configuration into dynamic behavior.

A resolver points to a Task and provides the input that Task needs. When Diagonal evaluates the resolver, it resolves any nested values first, runs the Task, and returns the Task output.

Resolver shape

Every resolver uses the same shape:

{
  "$type": "math.add",
  "$value": {
    "addend": 4,
    "augend": 8
  }
}

$type is the Task key. $value is the Task input.

Resolvers can appear inside workflow step inputs, field values, page runtime values, component props, conditional logic, and other resolver payloads. This lets one value come from another value instead of being hardcoded.

Tasks power resolvers

Tasks are the building blocks behind resolvers. A Task defines what can be executed, what inputs it accepts, what output it returns, and where it is allowed to run.

Task property What it means
Key Stable identifier used by resolvers, such as math.add.
Name Human-readable label shown in builders and selectors.
Description Short explanation of what the Task does.
Environment Whether the Task runs on the backend, frontend, or both.
Fields The input properties the Task accepts.
Schema The output shape the Task returns.
Exposed Whether users can reference the Task in resolver chains.

Execution environments

Resolvers are not tied to only one runtime. Each Task declares its environment.

Environment Where it runs Best for
Server Backend only Database work, workflow execution, payments, HTTP calls, messages, and secure operations.
Client Frontend only Page runtime behavior and browser-only UI effects.
Isomorphic Backend and frontend Pure logic such as math, text, list, date, and assertion helpers.

Some Tasks exist only on the backend. Some exist only in the frontend runtime. Isomorphic Tasks can be used in both places, which makes them useful for values that need to resolve consistently across workflows and pages.

Example: Math Add

math.add is an isomorphic resolver. It can run on the backend or frontend because it is pure math and does not depend on private server state or browser-only APIs.

Property Value
Key math.add
Name Math: Add
Description Add two numbers
Environment Isomorphic
Inputs addend, augend
Output Number

The resolver input supplies the two numbers:

{
  "$type": "math.add",
  "$value": {
    "addend": 10,
    "augend": 5
  }
}

The resolved value is 15.

Nested resolvers

Resolver inputs can contain other resolvers. Diagonal resolves from the inside out:

  1. Resolve primitive values as-is.
  2. Resolve nested resolver inputs.
  3. Run the Task named by $type.
  4. Recurse through nested objects and lists.

This is what makes resolvers composable. For example, a workflow can read a value from context, format it, compare it, and pass the result into a later Task without creating custom code for that exact path.

Common resolver categories

Category What it does
Context and inputs Read values from workflow context, page inputs, block props, or previous step output.
Math and text Transform values with pure calculations and string operations.
Lists and dictionaries Pick, join, map, filter, or reshape structured values.
Conditions and assertions Compare values and choose branches.
Dates and times Generate, format, and shift temporal values.
Data and integrations Read or write records, send requests, charge payments, and call external systems.

When to use a resolver

Use a resolver when a value should be calculated, loaded, transformed, or read from runtime context.

Use a static value when the value is known ahead of time and does not need to react to workflow input, page state, record data, or another Task output.

Start simple

Most resolver chains should be short. If a chain becomes difficult to read, move part of the behavior into a workflow step, a named variable, or a purpose-built Task.