Docs

Node Types

Reference for trigger, task, code, and human nodes, including configs and runtime context.

Gondo workflows use four node types: trigger, task, code, and human.

Shared node fields

Most nodes share this shape:

id: example_node
type: task
title: Example node
goal: One short sentence for people reading the graph.
integrations:
  - finance_gmail
config: {}
ui:
  x: 0
  y: 0

integrations lists the integration reference IDs the node is allowed to use.

Trigger nodes

Every published workflow has exactly one trigger: Manual, Auto, or Webhook. The trigger stays in the graph and its data schema describes the workflow inputs.

Manual trigger

Use a Manual trigger when the job runs only when explicitly requested.

type: trigger
title: Run
config:
  kind: manual
  name: review-invoices
  dataSchema:
    type: object
    properties:
      folderId:
        type: string
    required: [folderId]

The config.name field above names the manual trigger, not the workflow itself.

The trigger output is available downstream as ctx.data.<triggerNodeId>.

Auto trigger

Use an auto trigger when the workflow should run on a schedule.

type: trigger
title: Weekday check
config:
  kind: auto
  cron: '0 9 * * 1-5'
  timezone: Europe/London

An auto trigger can also run code before starting workflow runs.

type: trigger
title: New Gmail messages
integrations:
  - finance_gmail
config:
  kind: auto
  cron: '*/15 * * * *'
  code: |
    const gmail = getIntegration('finance_gmail')
    const messages = await (await gmail.get('/messages?q=invoice')).json()

    for (const message of messages.messages ?? []) {
      if (await store.get(`seen.gmail.${message.id}`)) continue
      await triggerWorkflow({ messageId: message.id })
      await store.set(`seen.gmail.${message.id}`, true)
    }
  dataSchema:
    type: object
    required: [messageId]
    properties:
      messageId:
        type: string

Auto trigger code can use store.get, store.set, store.delete, and triggerWorkflow(data). The runtime does not suppress identical inputs. Use stored markers, as above, or a provider’s incremental cursor to choose new work. Save the cursor returned by the provider after requesting the runs for that page; do not substitute the current time, which could skip items. Follow the provider’s pagination contract when processing longer lists.

Webhook trigger

A webhook starts when an external service sends an authenticated JSON request. Configure the endpoint and secret header in Studio; credentials are not part of YAML. Required code reads ctx.event.body, filters events, and calls triggerWorkflow(data) zero, one, or several times. Its dataSchema describes the resulting workflow inputs, not the provider event envelope.

type: trigger
title: Successful payment
config:
  kind: webhook
  code: |
    const event = ctx.event.body
    if (event.type !== 'payment.succeeded') return
    if (typeof event.id !== 'string' || !event.id) throw new Error('Missing payment event ID')
    const key = `payment.${event.id}`
    if (await store.get(key)) return
    await triggerWorkflow({ invoiceId: event.data.invoiceId })
    await store.set(key, true)
  dataSchema:
    type: object
    required: [invoiceId]
    properties:
      invoiceId:
        type: string
    additionalProperties: false

Duplicate handling is ordinary code: the example uses the sender’s event ID as a store key. There is no event-ID selector or automatic payload deduplication. To forward the body unchanged, write await triggerWorkflow(ctx.event.body). Zero calls ignore the event. Webhook code uses the same workspace helpers and limits as Auto. After successful code execution, the runtime creates all requested runs, then saves store changes. A failure between those steps can cause repeats on retry. Sample events in Studio immediately start real runs and update the same workflow store; use a fresh sample ID when you want another run.

Employees can explicitly run their own published jobs through any trigger kind. They supply the trigger's expected inputs directly; Auto discovery code and Webhook mapping code are bypassed. Do not add a second Manual trigger. Preserve trigger IDs when changing kinds, since downstream references depend on them.

Task nodes

A task node is an AI reasoning step.

Use it when the workflow needs judgement, classification, drafting, document reading, messy data handling, or adaptable browser work.

type: task
title: Classify invoice
goal: Decide whether the invoice needs review.
integrations:
  - finance_gmail
config:
  instruction: |-
    Read the invoice email identified by ctx.data.new_email.messageId.
    Return whether it needs human review.
    If the amount is missing, set needsReview to true and explain why.
  dataSchema:
    type: object
    required: [needsReview, reason]
    properties:
      needsReview:
        type: boolean
      reason:
        type: string

The task node returns structured data matching dataSchema.

Code nodes

A code node runs deterministic JavaScript.

Use it for API calls, structured writes, file movement in the workflow workspace, or transformations where the input shape is already clear.

type: code
title: Add row to sheet
goal: Write approved invoice data to the finance sheet.
integrations:
  - finance_sheets
config:
  code: |
    const sheets = getIntegration('finance_sheets')
    const invoice = ctx.data.classify_invoice
    const response = await sheets.post('/values/A1:append', {
      values: [[invoice.supplierName, invoice.amount]]
    })
    const result = await response.json()
    return { updatedRange: result.updates?.updatedRange ?? null }
  dataSchema:
    type: object
    required: [updatedRange]
    properties:
      updatedRange:
        type: [string, 'null']

Code nodes can use:

  • ctx.data
  • ctx.run
  • ctx.vars
  • getIntegration('<referenceId>')
  • log(...)

External provider calls must go through getIntegration().

Human nodes

A human node pauses the workflow.

Use it when a person should review, approve, edit, or provide information.

Human node UI is stored as a Vue single-file component in config.component. When completed, its final state becomes ctx.data.<nodeId>.

type: human
title: Approve invoice
goal: Ask finance to approve or reject the invoice.
config:
  dataSchema:
    type: object
    required: [decision, notes]
    properties:
      decision:
        type: string
        enum: [approved, rejected]
      notes:
        type: string
  component: |
    <template>
      <section>
        <p>Approve this invoice?</p>
        <button type="button" @click="approve">Approve</button>
      </section>
    </template>

    <script setup>
    const { submitAction } = defineProps({
      submitAction: { type: Function, required: true }
    })

    function approve() {
      submitAction('approve', { notes: '' })
    }
    </script>

    <script backend>
    export async function approve(state, inputs) {
      return { ...state, decision: 'approved', notes: inputs.notes, done: true }
    }
    </script>

Human nodes should render both an active input state and a completed recap state.