- Building Workflows
- Defining Functions
Defining Functions
A function is a workflow definition that responds to events. Functions contain one or more steps that execute in sequence, with automatic memoization for durability.
Basic Function Definition
import { ironflow } from "@ironflow/node";
export const processOrder = ironflow.createFunction( { id: "process-order", triggers: [{ event: "order.placed" }], }, async ({ event, step }) => { // Event data is typed via generics or casting const { orderId } = event.data as { orderId: string };
const result = await step.run("validate", async () => { return await validateOrder(orderId); });
return { success: true, result }; },);import "github.com/sahina/ironflow-go/ironflow"
var ProcessOrder = ironflow.CreateFunction(ironflow.FunctionConfig{ ID: "process-order", Triggers: []ironflow.Trigger{{Event: "order.placed"}},}, func(ctx ironflow.Context) (any, error) { var data OrderEvent if err := ctx.Event.Data(&data); err != nil { return nil, err }
result, err := ironflow.Run(ctx, "validate", func() (bool, error) { return validateOrder(data.OrderID) })
return map[string]any{"success": result}, err})Configuration Options
Functions accept a configuration object to tune retry logic, timeouts, and concurrency. The concurrency.key field uses dot-notation paths starting from the event root (e.g. event.data.orgId).
These are the common ones. For every field — including stepTimeout, recording,
recordingRetention, actorKey, name and metadata — see
FunctionConfig. Two fields have their own
pages: debounce and
cancelOn.
ironflow.createFunction( { id: "my-function", triggers: [{ event: "my.event" }], mode: "push", // "push" (default) or "pull" // Push execution budget in ms. It can only *tighten* the server's // `engine.pushTimeout` ceiling (default 10s), never raise it, and it is not // applied at all in pull mode (which is unlimited). timeout: 5_000, retry: { maxAttempts: 3, initialDelayMs: 1000, backoffFactor: 2.0, }, concurrency: { limit: 10, key: "event.data.orgId", // Group limits by this field }, secrets: ["STRIPE_KEY"], // Inject secrets at runtime }, handler,);import "time"
ironflow.CreateFunction(ironflow.FunctionConfig{ ID: "my-function", Triggers: []ironflow.Trigger{{Event: "my.event"}}, Mode: "pull", Retry: &ironflow.RetryConfig{ MaxAttempts: 5, InitialDelay: time.Second, BackoffFactor: 2.0, }, Concurrency: &ironflow.ConcurrencyConfig{ Limit: 10, Key: "event.data.orgId", }, Secrets: []string{"STRIPE_KEY"},}, handler)Event Data Access
The event object in the context contains the triggering event’s payload and metadata.
import { z } from "zod";
// Define your event schema with Zodconst orderPlacedSchema = z.object({ orderId: z.string(), amount: z.number(),});
// Pass the schema in the config for type-safe event dataexport const myFn = ironflow.createFunction( { id: "my-fn", triggers: [{ event: "order.placed" }], schema: orderPlacedSchema, }, async ({ event, step }) => { // event.data is now type-safe: { orderId: string, amount: number } console.log(event.data.orderId);
// Access user-defined metadata attached when the event was emitted const traceId = event.metadata?.traceId as string | undefined; const source = event.metadata?.source as string | undefined; },);The schema is enforced at runtime on every execution path (push, pull, streaming pull,
and the test harness). Before the handler runs, the SDK parses event.data with the
schema. On success, the handler receives the parsed value, so Zod defaults and transforms
apply and z.object() strips keys the schema does not declare (use .loose() to keep
them). On failure, the run fails with a non-retryable SchemaValidationError (code
VALIDATION_ERROR) that names the function and the first ten Zod issues; issue text can
include received values. The event record is kept, so you can inspect the payload in the
dashboard. Cron ticks are not validated: their payload is engine-generated, so a
function with both an event and a cron trigger keeps running on schedule. Functions
without a schema receive event.data unchanged.
func(ctx ironflow.Context) (any, error) { var data MyData // Unmarshal JSON data into your struct if err := ctx.Event.Data(&data); err != nil { return nil, err }
// Access user-defined metadata attached when the event was emitted traceId, _ := ctx.Event.Metadata["traceId"].(string) source, _ := ctx.Event.Metadata["source"].(string)
return data.ID, nil}Secrets Injection
If your function requires sensitive API keys, declare them in the secrets array. Ironflow will resolve these from its encrypted vault and inject them into the handler context at runtime.
async ({ event, step, secrets }) => { const apiKey = secrets.get("STRIPE_KEY");};func(ctx ironflow.Context) (any, error) { var apiKey string if err := ctx.Secrets.Get("STRIPE_KEY", &apiKey); err != nil { return nil, err } return apiKey, nil}What’s Next?
- Step Primitives — Learn about run, sleep, and waitForEvent
- Execution Modes — Push mode vs. Pull mode