> ## Documentation Index
> Fetch the complete documentation index at: https://openworkflow.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Type Safety

> End-to-end TypeScript type safety for workflows

OpenWorkflow is built with TypeScript and provides full type inference for
workflow inputs, outputs, and step results. You get autocomplete and compile-
time checking throughout.

## Schema Validation with Type Inference

For runtime validation with type inference, use [Standard
Schema](/docs/standard-schema) compatible libraries like Zod:

```ts theme={null}
import { z } from "zod";

const inputSchema = z.object({
  email: z.string().email(),
  amount: z.number().positive(),
});

export const payment = defineWorkflow(
  {
    name: "payment",
    schema: inputSchema,
  },
  async ({ input, step }) => {
    // input is typed as { email: string; amount: number }
    // and validated at runtime
  },
);
```

## Explicit Type Definitions

To manually specify input and (optionally) output types, use generics with
`defineWorkflow`:

```ts theme={null}
import { defineWorkflow } from "openworkflow";

interface OrderInput {
  orderId: string;
  customerId: string;
}

interface OrderResult {
  success: boolean;
  trackingNumber: string;
}

export const processOrder = defineWorkflow<OrderInput, OrderResult>(
  { name: "process-order" },
  async ({ input, step }) => {
    const validated = await step.run({ name: "validate" }, async () => {
      // input is typed as OrderInput
      return validateOrder(input.orderId);
    });

    const trackingNumber = await step.run({ name: "ship" }, async () => {
      return shipOrder(input.orderId);
    });

    // Return type must match OrderResult
    return {
      success: true,
      trackingNumber,
    };
  },
);
```

If you only specify the input type, the output type is inferred from the return
value:

```ts theme={null}
export const greet = defineWorkflow<{ name: string }>(
  { name: "greet" },
  async ({ input }) => {
    return `Hello, ${input.name}!`; // Output inferred as string
  },
);
```

## Workflow Specs

When using `defineWorkflowSpec` for separate declaration and implementation,
types flow through:

```ts theme={null}
import { defineWorkflowSpec, OpenWorkflow } from "openworkflow";

// Declare the workflow spec with types
export const emailSpec = defineWorkflowSpec<
  { to: string; subject: string },
  { sent: boolean }
>({
  name: "send-email",
});

// Implementation receives typed input
const ow = new OpenWorkflow({ backend });
ow.implementWorkflow(emailSpec, async ({ input, step }) => {
  // input.to and input.subject are typed
  await step.run({ name: "send" }, async () => {
    await sendEmail(input.to, input.subject);
  });
  return { sent: true };
});
```

## Client-Side Type Safety

When you run a workflow, the handle is typed with the output:

```ts theme={null}
const handle = await ow.runWorkflow(processOrder.spec, {
  orderId: "123",
  customerId: "456",
});

// result is typed as OrderResult
const result = await handle.result();
console.log(result.trackingNumber); // TypeScript knows this exists
```

## Step Result Types

Step return types are inferred automatically:

```ts theme={null}
const workflow = defineWorkflow<{ userId: string }>(
  { name: "user-workflow" },
  async ({ input, step }) => {
    // user is inferred as User type from the return
    const user = await step.run({ name: "get-user" }, async () => {
      const response = await fetch(`/api/users/${input.userId}`);
      return response.json() as User;
    });

    // TypeScript knows user has these properties
    console.log(user.email, user.name);
  },
);
```
