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

# Build workflows on the visual canvas

> Tour the ModuleX workflow builder: the drag-and-drop canvas, adding nodes and edges, the AI Composer copilot, and running a workflow live without leaving the page.

export const MediaEmbed = ({id, type = 'screenshot', caption = '', ext, ratio = '16 / 9'}) => {
  const isVideo = type === 'video' || type === 'app_video';
  const resolvedExt = ext || (isVideo ? 'mp4' : type === 'screenshot' ? 'webp' : 'svg');
  const src = 'https://media.modulex.dev/' + id + '.' + resolvedExt;
  const [status, setStatus] = useState('loading');
  const [isDev, setIsDev] = useState(false);
  const [inView, setInView] = useState(false);
  const boxRef = useRef(null);
  useEffect(() => {
    if (typeof window === 'undefined') return;
    const h = window.location.hostname;
    setIsDev(h === 'localhost' || h === '127.0.0.1' || h.endsWith('.mintlify.app'));
  }, []);
  useEffect(() => {
    if (inView) return;
    if (typeof IntersectionObserver === 'undefined') {
      setInView(true);
      return;
    }
    const el = boxRef.current;
    if (!el) return;
    const io = new IntersectionObserver(entries => {
      if (entries.some(e => e.isIntersecting)) {
        setInView(true);
        io.disconnect();
      }
    }, {
      rootMargin: '300px'
    });
    io.observe(el);
    return () => io.disconnect();
  }, [inView]);
  if (status === 'missing') {
    if (!isDev) return null;
    return <div style={{
      display: 'flex',
      flexDirection: 'column',
      justifyContent: 'center',
      gap: '0.4rem',
      padding: '1rem 1.25rem',
      margin: '1.25rem 0',
      width: '100%',
      aspectRatio: ratio,
      boxSizing: 'border-box',
      border: '1px dashed rgba(128,128,128,0.45)',
      borderRadius: '0.75rem',
      background: 'rgba(128,128,128,0.06)',
      color: 'currentColor',
      fontSize: '0.85rem',
      lineHeight: 1.45
    }}>
        <div style={{
      display: 'flex',
      alignItems: 'center',
      gap: '0.5rem',
      opacity: 0.75
    }}>
          <span aria-hidden="true">🎬</span>
          <code style={{
      fontSize: '0.75rem'
    }}>{id}</code>
          <span style={{
      fontSize: '0.65rem',
      textTransform: 'uppercase',
      letterSpacing: '0.04em',
      padding: '0.1rem 0.4rem',
      borderRadius: '0.4rem',
      background: 'rgba(128,128,128,0.18)'
    }}>
            {type}
          </span>
        </div>
        <div style={{
      opacity: 0.9
    }}>{caption || 'Media not uploaded yet.'}</div>
        <div style={{
      fontSize: '0.7rem',
      opacity: 0.5
    }}>
          Upload to R2 as <code>{id}.{resolvedExt}</code> — preview only, hidden in production.
        </div>
      </div>;
  }
  const mediaStyle = {
    display: status === 'loaded' ? 'block' : 'none',
    width: '100%',
    height: 'auto',
    borderRadius: '0.75rem'
  };
  const media = isVideo ? <video src={inView ? src : undefined} autoPlay loop muted playsInline preload="metadata" onLoadedData={() => setStatus('loaded')} onError={() => setStatus('missing')} style={mediaStyle} /> : <img src={inView ? src : undefined} alt={caption} onLoad={() => setStatus('loaded')} onError={() => setStatus('missing')} style={mediaStyle} />;
  return <figure style={{
    margin: '1.25rem 0'
  }}>
      <div ref={boxRef} style={status === 'loaded' ? {
    width: '100%'
  } : {
    width: '100%',
    aspectRatio: ratio,
    borderRadius: '0.75rem',
    background: 'rgba(128,128,128,0.06)'
  }}>
        {media}
      </div>
      {status === 'loaded' && caption ? <figcaption style={{
    marginTop: '0.5rem',
    textAlign: 'center',
    fontSize: '0.85rem',
    opacity: 0.7
  }}>
          {caption}
        </figcaption> : null}
    </figure>;
};

The workflow builder is where you assemble a workflow by hand: a visual canvas where each step is a node you drag in, connect, and configure. You can build the whole thing yourself, or describe what you want in plain English and let the [AI Composer](/concepts/ai-composer) draft it for you — then run it and watch each step light up, all on the same page.

This page is the tour. It covers what you see on the canvas, how to add and connect steps, how the Composer copilot fits in, and how to run a workflow from the builder.

<CardGroup cols={2}>
  <Card title="Canvas anatomy" icon="layout-dashboard" href="#canvas-anatomy">
    The map of the builder: the canvas, the start node, the detail panel, and the right-side dock.
  </Card>

  <Card title="Add nodes and edges" icon="plus" href="#add-nodes-and-edges">
    Drop in steps, connect them, and configure each one.
  </Card>

  <Card title="The Composer copilot" icon="sparkles" href="#the-composer-copilot">
    Describe a workflow in plain English and watch it appear on the canvas.
  </Card>

  <Card title="Run from the builder" icon="play" href="#run-from-the-builder">
    Run your workflow and follow each step live as it executes.
  </Card>
</CardGroup>

<MediaEmbed id="MX-MEDIA-3000" type="app_video" caption={"A short tour of the ModuleX workflow builder, end to end."} />

## Canvas anatomy

A workflow opens at `/workflow-builder/{id}`. The screen has three zones: the **canvas** in the middle, the **node dock** you pull steps from, and a **right-side panel** that switches between configuration, the Composer, runs, and outputs.

<MediaEmbed id="MX-MEDIA-3001" type="screenshot" caption={"The workflow builder with its three zones labelled."} />

### The canvas

The canvas is an infinite, pannable surface that holds your workflow graph. Each box is a **node** (one step) and each line is an **edge** (the path from one step to the next). You pan by dragging the empty canvas, zoom with the scroll wheel or the on-canvas controls, and select a node by clicking it. A minimap and zoom controls sit in a corner so you never lose your place in a large graph.

For the full set of canvas gestures, the minimap, and keyboard shortcuts, see [Canvas & controls](/workflow-builder/canvas).

### The start node

Every workflow begins at a single **start node**. It is the entry point — the place a run begins — and it is always present, so you cannot delete it. You connect your first real step to the start node, and the run follows the edges from there.

<Note>
  The start node is special on the wire too: it has the fixed id `__start__` and is a virtual node, which means it never appears in the workflow's saved `nodes` list. You will see it on the canvas, but you do not configure it like a regular step. See [Workflow engine & nodes](/concepts/workflow-engine) for how `__start__` and the end of a workflow are handled.
</Note>

### Nodes

A node is one step in your workflow. ModuleX has **nine** node types, and each does one job — call a model, run a tool, retrieve from your knowledge, branch on a condition, and so on. Whatever a node produces is written into the run's shared state under that node's name, so a later step can reuse it.

<CardGroup cols={3}>
  <Card title="LLM" icon="message-square" href="/workflow-builder/nodes/llm">
    Call a language model with a prompt.
  </Card>

  <Card title="Agent" icon="bot" href="/workflow-builder/nodes/agent">
    Let a model use tools in a loop to reach a goal.
  </Card>

  <Card title="Tool" icon="wrench" href="/workflow-builder/nodes/tool">
    Run one action from a connected integration.
  </Card>

  <Card title="Knowledge" icon="book-open" href="/workflow-builder/nodes/knowledge">
    Retrieve relevant context from a knowledge base.
  </Card>

  <Card title="Function" icon="function-square" href="/workflow-builder/nodes/function">
    Built-in helpers: HTTP requests, webhooks, validation.
  </Card>

  <Card title="Conditional" icon="git-branch" href="/workflow-builder/nodes/conditional">
    Branch on an expression, a model decision, or a loop.
  </Card>

  <Card title="Transformer" icon="shuffle" href="/workflow-builder/nodes/transformer">
    Reshape, map, and combine data between steps.
  </Card>

  <Card title="Guardrails" icon="shield-check" href="/workflow-builder/nodes/guardrails">
    Validate content with schema, regex, and PII checks.
  </Card>

  <Card title="Interrupt" icon="hand" href="/workflow-builder/nodes/interrupt">
    Pause to ask a person a question, then resume.
  </Card>
</CardGroup>

For a side-by-side of all nine types and how each one writes its result into the run, see [Node types overview](/workflow-builder/nodes/overview).

### The detail panel

Click a node to open the **detail panel** on the right. This is where you set what the node does: the model and prompt for an [LLM node](/workflow-builder/nodes/llm), the action and credential for a [tool node](/workflow-builder/nodes/tool), the branches for a [conditional node](/workflow-builder/nodes/conditional), and so on. Close the panel to go back to the full-canvas view.

The same panel is where the builder points you when something needs attention — for example, if a node is missing a credential, the builder flags it and the fix is to open the detail panel for that node.

### The right-side dock

The panel on the right has four tabs. You will move between them constantly as you build, test, and refine a workflow.

| Tab           | What it shows                                                                               |
| ------------- | ------------------------------------------------------------------------------------------- |
| **Inspector** | The detail panel for the node you have selected.                                            |
| **Chat**      | The [Composer copilot](#the-composer-copilot) — describe changes and let ModuleX make them. |
| **Outputs**   | The result each node produced on the most recent run.                                       |
| **Runs**      | The history of past runs for this workflow, with their status.                              |

<Note>
  When a workflow is shared with you as view-only, the dock opens on **Outputs** and the Composer chat is hidden — you can inspect and watch runs, but not edit.
</Note>

### Working together in realtime

The builder is multiplayer. When teammates open the same workflow, you see their presence and cursors, and edits appear for everyone live. To avoid two people fighting over the same step, a node can be **locked** while someone is editing it — if you try to change a locked node, the builder tells you who has it.

This live collaboration runs over a separate realtime channel from the one that streams your runs. For how edits sync between collaborators (and how changes made elsewhere, such as by the Composer, flow back to your canvas), see [Realtime co-editing & external sync](/workflow-builder/realtime-coediting) and [Canvas collaboration](/platform/collaboration/canvas).

## Add nodes and edges

Building a workflow is three repeated moves: add a node, connect it, configure it.

<Steps>
  <Step title="Add a node">
    Drag a node type from the node dock onto the canvas, or drop it where you want the step to sit. A new node appears ready to configure.
  </Step>

  <Step title="Connect it with an edge">
    Drag from one node's connection handle to another node to create an **edge**. The edge defines the order steps run in — the run follows edges from the start node onward.
  </Step>

  <Step title="Configure it">
    Click the node to open its detail panel and set what it does: a prompt, an integration action, a condition, and so on. The builder validates as you go and flags anything missing.
  </Step>

  <Step title="Pass data forward with references">
    To use an earlier step's result inside a later one, reference it with `{{node_id.field}}` — for example `{{node_llm_1.text}}`. ModuleX resolves the reference to the real value when the workflow runs.
  </Step>
</Steps>

<Note>
  The reference syntax `{{node_id.field}}` is how steps share data. A bare `{{node_id}}` pulls a whole node's output; a path like `{{node_abc.results[0].title}}` reaches into it. For the complete reference model — paths, defaults, and how lists are handled — see [Variables & references](/workflow-builder/variables-and-references) and [Workflow engine & nodes](/concepts/workflow-engine).
</Note>

### Editing, moving, and removing

You move a node by dragging it, rename or toggle it off from its detail panel, and duplicate or delete it from its controls. Disabling a node leaves it on the canvas but skips it at run time — the run is rewired around it automatically. Deleting a node removes it and its edges. Every one of these edits is shared with collaborators in realtime and saved as you work.

<Accordion title="Undo, redo, and how edits are saved">
  The builder keeps a local undo and redo history of your edits, so you can step backward and forward through recent changes. When you undo or redo, the builder works out exactly what changed and applies that change for everyone on the workflow, so collaborators stay in sync. You do not need a manual "save" button — edits persist as you make them.
</Accordion>

<Accordion title="What the builder checks before you run">
  As you edit, the builder validates the workflow in the background and surfaces problems before you run. The common checks are: the workflow has at least one step beyond the start node, the start node connects to something, no step is left stranded with no path from the start, and every step that needs a credential has one. Each warning links straight to the step that needs fixing — usually by opening its detail panel.
</Accordion>

## The Composer copilot

You do not have to place every node by hand. The **AI Composer** is a copilot built into the builder: describe the workflow you want in plain English, and it drafts the graph for you — adding nodes, wiring edges, and setting up state — right on your canvas.

Open the **Chat** tab in the right-side dock and type a request, for example:

```text Example Composer prompt theme={null}
Create a workflow that posts new leads to Slack and adds them to a Google Sheet.
```

As the Composer works, you watch it think: it streams its plan, the steps it is building, and the edits it makes. The canvas updates live as nodes and edges appear, so you see the workflow take shape rather than waiting for a finished result.

<Steps>
  <Step title="Describe what you want">
    Type a plain-English request in the Composer chat — a new workflow, or a change to the one you have open.
  </Step>

  <Step title="Watch it build on the canvas">
    The Composer streams its progress and applies edits to your canvas as it goes. Nodes and edges appear in place.
  </Step>

  <Step title="Answer if it asks">
    The Composer may pause to ask you something — a yes/no, a choice, a free-text detail, or to connect a credential for an integration. Answer in the chat and it continues.
  </Step>

  <Step title="Save or revert">
    When it finishes, review the changes on the canvas, then **Save** to keep them or **Revert** to undo the whole set in one step.
  </Step>
</Steps>

<Note>
  The Composer pausing to ask you something is **human-in-the-loop** — the same pattern an [interrupt node](/workflow-builder/nodes/interrupt) uses inside a running workflow. You answer in the chat and the Composer picks up where it left off. Learn how that resume works in [Human-in-the-loop (HITL) resume](/realtime/hitl).
</Note>

Composer chats are tied to you, not to one workflow, so you can carry a conversation across the workflows you are building. The Composer also costs managed usage — see [Run from the builder](#run-from-the-builder) below for how that is metered.

For the full Composer walkthrough in the builder, see [AI Composer in the builder](/workflow-builder/composer). For an end-to-end build from a single prompt, follow [Build a workflow with Composer](/guides/build-with-composer). For the concept behind it, see [AI Composer](/concepts/ai-composer).

<MediaEmbed id="MX-MEDIA-3002" type="app_video" caption={"The Composer building a workflow on the canvas from one prompt."} />

## Run from the builder

You do not need to deploy or leave the builder to try a workflow. Press **Run** and ModuleX executes the workflow you have on the canvas right now, streaming each step's progress back to you.

<Steps>
  <Step title="Press Run">
    The builder collects the current canvas as the workflow to execute and, if the workflow takes input, prompts you for it.
  </Step>

  <Step title="The run is admitted">
    Before any work starts, the run passes the [usage gate](/billing/usage-gating). If you are over a limit, the run is stopped here with a clear message and nothing is charged — see the note below.
  </Step>

  <Step title="Steps light up live">
    Each node moves from **pending** to **running** to **completed** (or **error**) as the workflow executes. You watch this happen on the canvas and in the **Outputs** tab.
  </Step>

  <Step title="Answer any pauses, or stop">
    If the workflow hits an [interrupt node](/workflow-builder/nodes/interrupt), it pauses for your input and resumes once you answer. You can also stop a run at any time.
  </Step>
</Steps>

Results stream in over [Server-Sent Events](/realtime/sse-streaming) rather than a polling loop, which is why steps update the instant they finish. The run also shows up in the **Runs** tab so you can revisit it later. For the full run lifecycle and how to read the live stream, see [Running workflows](/workflow-builder/execution/running).

<Warning>
  Running a workflow, retrieving from [managed knowledge](/platform/knowledge/managed), and using the [Composer](/concepts/ai-composer) all consume managed usage, which is metered in credits. If your plan's allowance is exhausted or a limit is hit, ModuleX returns a billing denial — the run is refused **before** it starts, so you are never charged for a run that did not begin. The denial carries a `{code, layer, key, current, limit, reason}` body and arrives as a `402`, `403`, or `429` depending on which limit you hit. See [Usage gating & limits](/billing/usage-gating) and [Credits & the billing model](/concepts/credits-billing).
</Warning>

### Run it from code instead

The same workflow can run programmatically. Authenticate with `Authorization: Bearer mx_live_…` and `X-Organization-ID`, then call `POST /workflows/run`. The synchronous response returns run metadata immediately while the result streams over SSE — the same stream the builder uses.

<CodeGroup>
  ```bash cURL theme={null}
  curl https://api.modulex.dev/workflows/run \
    -H "Authorization: Bearer mx_live_8s2Kd0pQ4rTv7Xw" \
    -H "X-Organization-ID: 5d9c2e7a-1f4b-4a6c-9e3d-0b8a1c2d3e4f" \
    -H "Content-Type: application/json" \
    -d '{
      "workflow_id": "wf_3b1c9a2e",
      "input": { "topic": "Q3 launch summary" },
      "stream": true
    }'
  ```

  ```python Python theme={null}
  import asyncio
  from modulex import Modulex

  async def main():
      async with Modulex(
          api_key="mx_live_8s2Kd0pQ4rTv7Xw",
          organization_id="5d9c2e7a-1f4b-4a6c-9e3d-0b8a1c2d3e4f",
      ) as client:
          run = await client.executions.run(
              workflow_id="wf_3b1c9a2e",
              input={"topic": "Q3 launch summary"},
              stream=True,
          )
          print(run.run_id, run.status)

  asyncio.run(main())
  ```

  ```javascript JavaScript theme={null}
  import { Modulex } from 'modulex-js';

  const client = new Modulex({
    apiKey: 'mx_live_8s2Kd0pQ4rTv7Xw',
    organizationId: '5d9c2e7a-1f4b-4a6c-9e3d-0b8a1c2d3e4f',
  });

  const run = await client.executions.run({
    workflowId: 'wf_3b1c9a2e',
    input: { topic: 'Q3 launch summary' },
    stream: true,
  });

  // Responses stay snake_case even in the JS SDK.
  console.log(run.run_id, run.status);
  ```
</CodeGroup>

To run a saved workflow by `workflow_id`, it needs an active [deployment](/workflow-builder/execution/deploy). For the complete REST and SDK walkthrough — authenticate, run, and consume the stream in all three languages — see [Run a workflow (REST + SDK)](/guides/run-a-workflow) and [Run via API](/workflow-builder/execution/api-endpoint).

## Where to go next

<CardGroup cols={2}>
  <Card title="Canvas & controls" icon="mouse-pointer-2" href="/workflow-builder/canvas">
    Pan, zoom, select, the detail panel, and keyboard shortcuts.
  </Card>

  <Card title="Node types overview" icon="boxes" href="/workflow-builder/nodes/overview">
    All nine node types and what each one does.
  </Card>

  <Card title="AI Composer in the builder" icon="sparkles" href="/workflow-builder/composer">
    Build and edit your workflow by chatting with the copilot.
  </Card>

  <Card title="Running workflows" icon="play" href="/workflow-builder/execution/running">
    Run from the builder and read the live event stream.
  </Card>

  <Card title="Variables & references" icon="braces" href="/workflow-builder/variables-and-references">
    Pass data between steps with the reference system.
  </Card>

  <Card title="Realtime co-editing" icon="users" href="/workflow-builder/realtime-coediting">
    How canvas edits sync between collaborators and from elsewhere.
  </Card>
</CardGroup>
