---
title: JSX API
description: Author workflows as JSX trees. Smithers renders the tree, dispatches ready tasks, persists outputs, and re-renders. Branching and parallelism are plain JSX conditionals.
---

Workflows are JSX trees. Smithers renders the tree, extracts ready tasks, executes them, persists their outputs, and re-renders. Branching, looping, and parallelism are normal JSX.

<Note>API reference: [Authoring](/reference/authoring) and [Components](/reference/components) list every authoring helper and JSX element, their options, and links to source and tests.</Note>

## Setup

Most projects should use `bunx smithers-orchestrator init`; it scaffolds everything below.

To embed into an existing codebase:

```bash
bun add smithers-orchestrator zod
bun add -d typescript @types/react @types/node
```

`smithers-orchestrator` bundles React and ships the JSX runtime as
`smithers-orchestrator/jsx-runtime`, so you do **not** add `react` or `react-dom`
yourself. `jsxImportSource` (below) routes JSX through the workflow reconciler,
never React DOM. The one required dev dep is `@types/react` (React ships no
bundled types, and the JSX transform resolves its JSX namespace from it). The
browser UI surface (`smithers-orchestrator/gateway-react`) is the only place that
takes `react`/`react-dom` as peers.

Minimal `tsconfig.json`:

```json
{
  "compilerOptions": {
    "target": "ESNext",
    "module": "ESNext",
    "moduleResolution": "bundler",
    "jsx": "react-jsx",
    "jsxImportSource": "smithers-orchestrator",
    "strict": true,
    "noEmit": true,
    "skipLibCheck": true
  }
}
```

`jsxImportSource` is the only non-standard line; it routes JSX through `smithers-orchestrator/jsx-runtime` instead of React DOM.

Optional MDX prompts: add `bun add -d @types/mdx` and a `preload.ts` that calls `mdxPlugin()`, register it in `bunfig.toml` as `preload = ["./preload.ts"]`.

Verify with `bunx tsc --noEmit` and `bunx smithers-orchestrator --help`.

## A minimal workflow

```tsx
// @jsxImportSource smithers-orchestrator (only needed if not set in tsconfig.json)
import { createSmithers, Sequence, Task } from "smithers-orchestrator";
import { z } from "zod";

const { Workflow, smithers, outputs } = createSmithers({
  input: z.object({ repo: z.string() }),
  analysis: z.object({ summary: z.string() }),
});

export default smithers((ctx) => (
  <Workflow name="analyze">
    <Sequence>
      <Task id="analyze" output={outputs.analysis}>
        {{ summary: `Analyze ${ctx.input.repo}` }}
      </Task>
    </Sequence>
  </Workflow>
));
```

`createSmithers` is a named export. The lowercase `smithers` wrapper is not a
top-level import; it is the property returned by `createSmithers(...)`.
`smithers((ctx) => ...)` returns the `SmithersWorkflow` value to export from the
workflow file. The `input` schema above types `ctx.input.repo`; omit `input` and
`ctx.input` is `unknown`, so you must guard or parse it yourself.

`outputs.analysis` is the typed reference for the Zod schema, so typos are compile errors. The task body is a JSX expression (`{...}`) whose value is a plain object, with no LLM call here, just a static return. Real tasks pass a `run` prop or an AI model. See the [Task component reference](/components/workflow).

## Reactivity

The tree re-renders on every frame, so branching is a normal JSX conditional. Inside a workflow function, `ctx` exposes `ctx.input` and `ctx.outputMaybe(ref, { nodeId })`. The latter returns the output of a completed task, or `undefined` if it hasn't run yet:
The `ctx` parameter is optional: it is just a normal function argument, so a workflow that never reads `ctx.input` or `ctx.outputMaybe` can drop it entirely and write `smithers(() => ( ... ))`. Add `(ctx)` only when you actually reference dynamic input or prior task output.

```tsx
const analysis = ctx.outputMaybe(outputs.analysis, { nodeId: "analyze" });
{analysis ? <Task id="report" output={outputs.report} agent={writer}>...</Task> : null}
```

The `report` Task doesn't exist in the plan until `analysis` completes. No placeholder, no skipped node. The conditional IS the dependency. Unlike static DAG tools that require you to declare optional nodes upfront, the JSX conditional is evaluated fresh each frame: if `analysis` is undefined, the `report` task simply doesn't exist in that frame's plan.

For the common case of a task simply consuming one upstream output, `<Task deps={{ analyze: outputs.analysis }}>` with a `(deps) => ...` children callback expresses the same gating more directly; reach for `ctx.outputMaybe` when the logic is structural (branching on content, loops, counts) rather than plain existence.

## Read next

- [Tour](/tour): six-step worked example with agents, schemas, approvals, resume.
- [How It Works](/how-it-works): the render → execute → persist loop.
- [Components](/components/workflow): full prop surface for every JSX element.
