---
sidebar_label: Building blocks
toc_max_heading_level: 3
doc_id: a81f56a6-f422-49d1-844e-fc7092569493
description: >-
  The pieces every nullplatform workflow is made of: steps, connections,
  triggers, expressions, secrets, and runs.
keywords:
  - workflows
  - building blocks
  - steps
  - nodes
  - workflow model
  - nullplatform
---

import WorkflowCanvas from '@site/src/components/WorkflowCanvas';
import anatomy from '@site/src/components/WorkflowCanvas/examples/anatomy';

# Workflow building blocks

Every workflow, from a two-node hello world to a full drift-detection suite, is assembled from the same small set of pieces. This section covers what each piece does and when to use it.

## How a workflow is put together

A workflow is a graph of connected steps. Each step runs a plugin: query the [data lake](/docs/data-lake/), call a nullplatform API, run code, send a Slack message, or hand a task to AI. Data flows between steps as [items](/docs/workflows/building-blocks/items-and-data-flow) (plain JSON objects), and [expressions](/docs/workflows/building-blocks/expressions) wire one step's output into the next step's input. A step that receives several items usually runs once per item; that's called fan-out.

Steps come in three kinds. **Triggers** start a run and feed it inputs. **Modules** do the work. **Deciders** look at the data and route it to one of their output ports, which is how a workflow branches.

Two rules apply to every workflow:

- **Saving is always safe.** Every save creates a new immutable version. Nothing runs, nothing gets registered.
- **Activation is the moment side effects begin.** Activating a workflow registers its [triggers](/docs/workflows/building-blocks/triggers): schedules start firing, webhook URLs go live, event subscriptions start listening. Deactivating removes them all.

## Example: a scan-and-track workflow

Here's a representative scan-and-track workflow with all three step kinds. Click a node to see the YAML behind it:

<WorkflowCanvas workflow={anatomy} height={340} />

In the definition, steps declare what they run and connections declare how data flows between them:

```yaml
name: "Stale scope scan"
steps:
  - id: start_cron              # trigger: starts the run
    type: trigger
    plugin_type: cron
    config:
      schedule: "0 6 * * *"

  - id: lake_query              # module: does the work
    type: module
    plugin_type: np-lake-query
    config:
      apiKey: "${{ secrets.NP_API_KEY }}"   # credentials come from config entries
      sql: "SELECT ..."

  - id: anything_found          # decider: routes to output ports
    type: decider
    plugin_type: conditional
    config:
      expression: "steps.detect.outputs.total > 0"

connections:
  - { from: start_cron, to: lake_query }
  - { from: anything_found, to: open_items,  source_port: "true" }
  - { from: anything_found, to: log_summary, source_port: "false" }
```

Notice what's **not** in the definition: no credentials, no organization ids. Everything tenant-specific comes from [secrets and variables](/docs/workflows/building-blocks/secrets-and-variables), so the same definition runs unchanged in any organization.

You rarely write this YAML from scratch. The visual editor (**Platform Settings → Workflow Editor**) and the definition are two views of the same thing: build on the canvas and the YAML updates, paste YAML and the canvas redraws. Describing the workflow in plain language, as in [Your first workflow](/docs/workflows/getting-started), generates it for you.

## Next steps

- [Triggers](/docs/workflows/building-blocks/triggers): what starts a workflow
- [Nodes](/docs/workflows/building-blocks/nodes): the units of work
- [AI nodes](/docs/workflows/building-blocks/ai-nodes): agents as workflow steps
- [Items and data flow](/docs/workflows/building-blocks/items-and-data-flow): how data moves, and when a step runs more than once
- [Expressions](/docs/workflows/building-blocks/expressions): passing data between steps
- [Loops and iteration](/docs/workflows/building-blocks/loops): repeating work over a collection
- [Error handling](/docs/workflows/building-blocks/error-handling): retries, fallbacks, and failure lanes
- [Secrets and variables](/docs/workflows/building-blocks/secrets-and-variables): configuration without hardcoding
- [Runs and versions](/docs/workflows/building-blocks/runs-and-versions): executions, logs, and activation
- [Limits](/docs/workflows/building-blocks/limits): step timeouts, payload budgets, and the errors that name them
