Skip to content

Workflows ​

A workflow is an automated process made of nodes (steps) connected by edges. Each node does one thing: query the API, branch on a condition, call a URL, send a notification, wait for a person, run a Lua script, and so on. Data flows along the edges, and each node can read the workflow input and the output of the nodes before it.

You build workflows in the console's visual editor (Workflows, /#/workflows) or write the definition as YAML or JSON and import it through the API. Runs are listed under Executions (/#/workflow-executions).

This section has four pages:

  • Workflows (this page): definitions, versions, running and executions
  • Expressions: how node settings reference data
  • Node reference: every node type
  • Triggers: starting workflows on a schedule or on events

A workflow definition ​

A definition has three lists: nodes, edges and, optionally, variables. This workflow loads a job, checks its quantity and logs a different message for large and small runs:

yaml
nodes:
  - id: fetch_job
    name: Fetch job
    type: graphql
    config:
      operation: query
      query: 'query GetJob($id: JobID!) { getJob(id: $id) { id name quantity status } }'
      variables:
        id: "{{ input.jobId }}"
    maxRetries: 2
    timeoutSeconds: 15
  - id: is_large
    name: Large run?
    type: condition
    config:
      expression: nodes.fetch_job.output.data.getJob.quantity > 1000
  - id: log_large
    name: Log large run
    type: log
    config:
      level: warning
      message: "Large run: {{ nodes.fetch_job.output.data.getJob.name }} ({{ nodes.fetch_job.output.data.getJob.quantity }} copies)"
  - id: log_small
    name: Log small run
    type: log
    config:
      message: "Small run: {{ nodes.fetch_job.output.data.getJob.name }}"
edges:
  - id: e1
    fromNodeId: fetch_job
    toNodeId: is_large
  - id: e2
    fromNodeId: is_large
    toNodeId: log_large
    condition: "true"
  - id: e3
    fromNodeId: is_large
    toNodeId: log_small
    condition: "false"

Run it with the input {"jobId": "job_..."}. The workflow needs a service account that can read jobs, or the graphql node fails with an authentication error.

Service account ​

A workflow acts as its service account, not as the person or trigger that starts it. The graphql, script, custom_workflow_action and integration_action nodes, and the data query of an event trigger, all run with that account's permissions. A workflow with no service account can't make authenticated calls.

Create a service account in the console (Service accounts), attach an IAM policy with the permissions the workflow needs (job:read for the example above), and pick it under Service account on the workflow's Settings tab. Through the API, set botUserId on the workflow.

Nodes ​

FieldRequiredMeaning
idyesUnique within the workflow. Other nodes refer to this node's output by its id.
nameyesDisplay name in the editor and in execution steps
typeyesThe node type, for example graphql or condition. See the node reference.
configdepends on typeThe node's settings. Each type has its own config schema.
timeoutSecondsnoTime limit for this node. Without it, the workflow's defaultNodeTimeoutSeconds applies.
maxRetriesnoHow often a failing node is retried before it counts as failed
continueOnErrornotrue lets the workflow carry on when this node fails, instead of failing the whole execution
positionno{x, y} position in the visual editor

Edges ​

An edge connects fromNodeId to toNodeId. Edges leaving a branching node carry a condition label that names the branch they belong to. For a condition node the labels are "true" and "false". For a switch node they are the case names.

Variables ​

The optional variables list declares workflow variables with a name, a type, an optional defaultValue and a description. Declared variables start with their default value, and a set_variable node can change them during the run. Any node can read them as variables.<name>.

How data flows ​

  • The workflow input is a JSON object, readable everywhere as input.<field>.
  • Each node's result is readable by later nodes as nodes.<id>.output.<field>.
  • Node settings use expressions to reference both.

Nodes run in waves: a node starts once every node before it has finished, and independent branches run in parallel.

Branching and joining ​

A plain node has one input and one output: at most one edge in and one edge out. Validation rejects a plain node with two outgoing or two incoming edges. Fan-out and fan-in are always explicit:

NodeInOutUse
split1manyRun all outgoing branches in parallel
condition1manyTake the true or the false branch
switch1manyTake the first matching case, or the default
logic1manyCombine several conditions (and, or, xor…) into one true/false branch
joinmany1Wait for all live branches (AND) and combine their outputs
mergemany1Combine whichever branches arrive (OR)

Branches not taken are ignored by join and merge. The two differ only when a branch fails under continueOnError: merge drops the failed branch and goes on, while join applies its onPartialFailure setting (fail, the default, or skip).

Loops are not built with edges. The for_each node runs an embedded body node once per array item and outputs the collected results.

Versions and activation ​

A workflow (WorkflowState) holds settings such as its name, description and defaultNodeTimeoutSeconds. The definition lives in numbered versions (WorkflowVersionState).

  • Every saved definition is a new version. Versions record whether they passed validation (isValid, validationErrors).
  • The workflow runs the version named by its currentVersionId. Creating a version does not change it: you activate a version explicitly.
  • In the console editor, Save Version (on the Flow tab) creates a version and Publish makes the selected version the active one.
  • A workflow with no active version cannot run. executeWorkflow fails with "No active version set for workflow".

With the API:

graphql
mutation CreateWorkflow($input: CreateWorkflowInput!) {
  createWorkflow(input: $input) {
    workflow { id name currentVersionId }
    errors { path message }
  }
}

mutation ImportDefinition($input: ImportWorkflowInput!) {
  importWorkflow(input: $input) {
    version { id version isValid validationErrors }
    errors { path message }
  }
}

mutation Activate($workflowId: WorkflowID!, $versionId: WorkflowVersionID) {
  setActiveVersion(workflowId: $workflowId, versionId: $versionId) {
    workflow { id currentVersionId }
    errors { path message }
  }
}

importWorkflow takes the definition as a YAML or JSON string:

json
{
  "input": {
    "workflowId": "wfl_...",
    "format": "YAML",
    "content": "nodes:\n  - id: hello\n    name: Hello\n    type: log\n    config:\n      message: Hello\nedges: []\n"
  }
}

createWorkflowVersion does the same with a structured definition object instead of a string. To check a definition without storing it, use validateWorkflow. To get a stored version back as YAML or JSON, use exportWorkflow:

graphql
mutation Validate($content: String!) {
  validateWorkflow(input: { content: $content, format: YAML }) {
    isValid
    errors { path message }
  }
}

mutation Export($versionId: WorkflowVersionID!) {
  exportWorkflow(input: { versionId: $versionId, format: YAML }) {
    content
    errors { path message }
  }
}

Validation checks the graph (unknown node types, arity, dangling edges), each node's config against its type's schema, and every expression.

Running a workflow ​

Start a run from the console, from a trigger, or with executeWorkflow. The input is a JSON string containing an object. Omit versionId to run the active version.

graphql
mutation Run($input: ExecuteWorkflowInput!) {
  executeWorkflow(input: $input) {
    execution { id status startedAt }
    errors { path message }
  }
}
json
{
  "input": {
    "workflowId": "wfl_...",
    "input": "{\"jobId\": \"job_...\"}"
  }
}

The mutation returns as soon as the execution is queued. Follow its progress with getExecution:

graphql
query Execution($id: WorkflowExecutionID!) {
  getExecution(id: $id) {
    id
    status
    input
    output
    durationMs
    waitingNodeId
    error { code message nodeId }
    steps {
      nodeId
      nodeName
      nodeType
      wave
      status
      durationMs
      output
      error
    }
  }
}

An execution is PENDING, RUNNING, WAITING, COMPLETED, FAILED or CANCELLED (ExecutionStatus). WAITING means it is paused at waitingNodeId, for example on a human task node. input, output and each step's input and output are JSON strings.

List recent runs of a workflow:

graphql
query RecentRuns($workflowId: ID!) {
  listExecutions(
    first: 20
    filter: { workflowId: { eq: $workflowId } }
    sort: [{ field: STARTED_AT, direction: DESC }]
  ) {
    edges {
      node { id status startedAt durationMs }
    }
  }
}

cancelExecution(id:) stops a running execution. resumeWorkflowExecution(id:) resumes one that is waiting for an external signal.

Connect machines, automate workflows, and build custom apps for print production.