Skip to content

Inputs, variables, and TypeScript

Durable workflows do not have a separate variable language. Use ordinary TypeScript locals and control flow inside run().

Define run inputs and final outputs with schema. Supported values include strings, booleans, finite numbers, safe integers, bigint, uint256, int256, addresses, bytes, hashes, chain IDs, controlled-account IDs, arrays, tuples, objects, enums, and discriminated unions.

Use label() and describe() to make browser run forms clear:

input: schema.object({
amount: schema
.uint256()
.label("Amount")
.describe("Amount of ETH to transfer in wei"),
note: schema.string().optional().describe("Optional note for this run"),
})

The browser humanises an unlabelled property name. A description appears as restrained field help, an optional field can be added or removed, and a default value is prefilled. Nested objects, tuples, arrays, and discriminated unions use the same schema recursively.

Integer fields include an inline unit calculator. For example, 0.1 ETH produces the raw value 100000000000000000 for a uint256 field.

Run inputs contain literal values only. They do not reference other fields or contain expressions. Calculate derived values inside run() with ordinary TypeScript:

const total = input.amount * BigInt(input.count)

The advanced JSON editor uses tagged wire values for integers and bytes. The SDK converts them so a contract result or schema.uint256() input is a native bigint in the program:

const total = input.pricePerItem * BigInt(input.count)
const buffered = (total * 10_050n) / 10_000n

Do not convert EVM integers to JavaScript number.

Locals, closures, loops, and branches work normally because Cloudflare Workflows resumes the durable operation and txcast effects have stable keys:

const calls = []
for (let index = 0; index < input.count; index += 1) {
calls.push({
kind: "registry-abi" as const,
contractKey: "1:0x1111111111111111111111111111111111111111",
functionKey: "acquire(uint256)",
args: [BigInt(index)],
})
}

A key inside a loop must be unique and deterministic, for example acquire-${index}. The runtime count is governed by run and payload limits, not by an authoring-language loop bound.

Sequential await, Promise.all, and Promise.allSettled are supported. Runtime-sized parallel frontiers use one local const array, standalone .push(effect) calls in the same lexical block, and one direct await or return of Promise.all or Promise.allSettled.

Promise.race, Promise.any, direct promises/thenables, aliased or floating effect arrays, duplicate keys, and unawaited effects are rejected.

Workflow code may import the pinned @txcast/workflow-sdk and reviewed pure viem utilities. It cannot use direct fetch, arbitrary npm packages, filesystem, environment, clock, randomness, dynamic imports, eval, or Function. Use a durable effect for every external observation or action.