Skip to content

Executables

Executables are the building blocks of flow automation. They can be simple commands, complex multi-step workflows, HTTP requests, or even GUI applications. This guide covers all executable types and configuration options.

Finding Executables

Use the flow browse command to discover executables across your workspaces:

shell
flow browse            # Interactive multi-pane browser
flow browse --list     # Simple list view
flow browse VERB ID    # View specific executable details

Filter executables by workspace, namespace, verb, or tag:

shell
flow browse --workspace api --namespace v1 --verb deploy --tag production
flow browse --all --filter "database"  # Search names and descriptions

Executable Configuration

Basic Structure

Every executable needs a verb and optionally a name:

yaml
executables:
  - verb: run
    name: my-task
    description: "Does something useful"
    tags: [development, automation]
    aliases: [task, job]
    timeout: 5m
    visibility: public
    exec:
      cmd: echo "Hello, world!"

Common Fields

  • verb: Action type (run, build, test, deploy, etc.)
  • verbAliases: Alternative names for the verb
  • name: Unique identifier within the namespace
  • description: Markdown documentation for the executable
  • tags: Labels for categorization and filtering
  • aliases: Alternative names for the executable
  • timeout: Maximum execution time (e.g., 30s, 5m, 1h)
  • visibility: Access control (public, private, internal, hidden)

Visibility Levels

  • public: Available from any workspace
  • private: Only available within the same workspace but shown in browse lists (default)
  • internal: Available within workspace but hidden from browse lists
  • hidden: Cannot be run or listed

Environment Variables

Customize executable behavior with environment variables or temporary files using params or args.

INFO

Executables inherit environment variables from their parent executable, workspace, and system.

By default, values defined in the .env file at the workspace root are automatically loaded. This can be overriden in the workspace configuration file with the envFiles field.

Parameters (params)

Set environment data from various sources:

yaml
executables:
  - verb: deploy
    name: app
    exec:
      file: deploy.sh
      params:
        # From secrets
        - secretRef: api-token
          envKey: API_TOKEN
        - secretRef: production/database-url
          envKey: DATABASE_URL

        # Interactive prompts
        - prompt: "Which environment?"
          envKey: ENVIRONMENT

        # Static values
        - text: "production"
          envKey: DEPLOY_ENV

        # Env File (key=value format)
        - envFile: "development.env"
        - envFile: "staging.env"
          envKey: SHARED_KEYS  # Only load specific keys

        # Saved to a file
        - secretRef: tls-cert
          outputFile: cert.pem

Parameter types:

  • secretRef: Reference to vault secret
  • prompt: Interactive user input
  • text: Static value
  • envFile: Load environment variables from a file

Arguments (args)

Handle command-line arguments:

yaml
executables:
  - verb: build
    name: container
    exec:
      file: build.sh
      args:
        # Positional argument
        - pos: 1
          envKey: IMAGE_TAG
          required: true

        # Flag arguments
        - flag: publish
          envKey: PUBLISH
          type: bool
          default: false

        - flag: registry
          envKey: REGISTRY
          default: "docker.io"

        # Saved to a file
        - flag: version
          outputFile: //version.txt

Run with arguments:

shell
flow build container -- v1.2.3 --publish=true --registry=my-registry.com

WARNING

Breaking change: Executable arguments now use standard --flag=value syntax with a -- separator. The previous flag=value format (e.g., flow build container v1.2.3 publish=true) is no longer supported. Use -- to separate flow flags from executable arguments, and prefix flag names with --.

Argument types:

  • pos: Positional argument (by position number, starting from 1)
  • flag: Named flag argument

Command-Line Overrides

Override any environment variable with --param:

shell
flow deploy app --param API_TOKEN=override --param ENVIRONMENT=staging

NOTE

If the outputFile field is used to save a value, it will automatically be cleaned up after the executable finishes running.

Working Directories

Control where executables run with the dir field:

yaml
executables:
  - verb: build
    name: frontend
    exec:
      cmd: npm run build
      dir: "./frontend"  # Relative to flowfile

  - verb: clean
    name: downloads
    exec:
      cmd: rm -rf downloads/*
      dir: "~/Downloads"  # User home directory

  - verb: deploy
    name: from-root
    exec:
      cmd: kubectl apply -f k8s/
      dir: "//"  # Workspace root

  - verb: test
    name: isolated
    exec:
      cmd: |
        echo "Running in temporary directory"
        ls -la
      dir: "f:tmp"  # Temporary directory (auto-cleaned)

Directory prefixes:

  • //: Workspace root directory
  • ~/: User home directory
  • ./: Current working directory
  • f:tmp: Temporary directory (auto-cleaned)
  • $VAR: Environment variable expansion

Executable Types

exec - Shell Commands

Run commands or scripts directly:

yaml
executables:
  - verb: build
    name: app
    exec:
      cmd: npm run build && npm test

  - verb: deploy
    name: app
    exec:
      file: deploy.sh
      logMode: json  # text, logfmt, json, or hidden

Options:

  • cmd: Inline command to run
  • file: Script file to execute
  • interpreter: Which interpreter runs cmdsh (default) or python (see below)
  • logMode: How to format command output
  • container: Run the command or file inside a container image (see below)

Running Python

Set exec.interpreter to python to run cmd as a Python script instead of a shell command:

yaml
executables:
  - verb: run
    name: report
    exec:
      interpreter: python
      cmd: |
        import json, sys
        print(json.dumps({"python": sys.version_info[:2]}))

Parameters, arguments, and secrets reach the script through the environment exactly as they do for a shell command, so os.environ is how you read them.

A .py file needs no interpreter at all — the extension implies it:

yaml
exec:
  file: scripts/analyze.py

Setting interpreter explicitly overrides whatever the extension would have implied.

How flow finds Python. A project's virtualenv wins over bare system Python, so a script gets the dependencies its repository installed:

OrderSource
1FLOW_PYTHON_BIN, if set
2$VIRTUAL_ENV — an activated virtualenv
3<workspace root>/.venv
4python3 on the PATH
5python on the PATH

FLOW_PYTHON_BIN is an environment variable rather than a field, so you can pin an interpreter for a whole workspace in its .env file, or for one executable via params. If it is set but does not resolve, the run fails rather than silently falling back to a different interpreter.

On Windows the virtualenv path is Scripts\python.exe, and python is preferred over python3python3.exe there is usually the Microsoft Store alias stub rather than a real interpreter.

flow runs cmd from a temporary file rather than python -c, which keeps your code out of the process table and means tracebacks carry real line numbers. It also sets PYTHONUNBUFFERED=1 so output streams as it is produced, and PYTHONDONTWRITEBYTECODE=1 to keep __pycache__ out of your workspace. Set either variable yourself to override.

Running in a container

Set exec.container to run the command inside a container instead of on the host. This gives you a pinned, reproducible toolchain without installing it locally. Requires docker or podman on the PATH.

yaml
executables:
  - verb: build
    name: go-app
    exec:
      cmd: go build -o bin/app ./cmd/app
      container:
        image: golang:1.21-alpine

The workspace root is auto-mounted (by default at /workspace) and the executable's directory becomes the working directory inside the container, so relative paths behave the same as they would on the host. Parameters, arguments, and FLOW_* variables are passed in automatically (secrets go through a temporary --env-file, never the command line).

Advanced options:

yaml
exec:
  cmd: npm test
  container:
    image: node:18-alpine
    runtime: auto          # auto (default), docker, or podman
    workdir: /app          # override the working directory
    mountWorkspace: /app   # container path for the workspace mount
    volumes:
      - "//cache:/cache"   # //-, ~/-, ./-, or absolute host paths
    inheritEnv: true       # pass params/args/FLOW_* into the container (default true)
    entrypoint: ""         # "" uses the image's ENTRYPOINT; unset defaults to sh
    user: "1000:1000"      # defaults to the host user on Linux
    network: host

interpreter: python works with container too — combine them to get a pinned Python toolchain without installing it locally:

yaml
exec:
  interpreter: python
  cmd: |
    import sys
    print(sys.version)
  container:
    image: python:3.13-alpine

Notes and limitations:

  • By default flow overrides the image entrypoint with sh so cmd behaves as a shell command on any image. With interpreter: python the default entrypoint becomes python3 instead. Set entrypoint: "" to use the image's own ENTRYPOINT — with Python that only works if the image's ENTRYPOINT is itself an interpreter.
  • Host interpreter discovery does not apply inside a container: the image's own python3 is used, and VIRTUAL_ENV, PYTHONPATH, PYTHONHOME, and FLOW_PYTHON_BIN are dropped from the container environment because those host paths mean nothing inside it. Install dependencies in the image, or mount them with volumes.
  • On Linux, flow runs as your host user by default so mounted files are not root-owned. Set user: root to opt out.
  • .bat, .cmd, and .ps1 files are not supported with container.
  • container applies to exec executables only; inline cmd steps inside serial/parallel do not inherit it — reference a container-backed executable instead. (interpreter is available on those steps — see below.)
  • outputFile destinations for params/args should resolve under the workspace root so the container can see them (use //-prefixed or flow-file-relative paths).
  • On macOS, Docker Desktop does not share /var/folders by default, so dir: f:tmp may fail to mount; use a workspace-relative directory instead.

Per-step interpreters

Inline cmd steps inside serial and parallel take their own interpreter, so one workflow can mix shell and Python without splitting into separate executables:

yaml
executables:
  - verb: run
    name: pipeline
    serial:
      execs:
        - cmd: ./fetch-data.sh
        - cmd: |
            import json
            print(json.load(open("data.json"))["total"])
          interpreter: python

A step that omits interpreter runs under the shell as before. A step using ref ignores the field — the referenced executable brings its own.

serial - Sequential Execution

Run multiple steps in order:

yaml
executables:
  - verb: deploy
    name: full-stack
    serial:
      failFast: true  # Stop on first failure
      execs:
        - cmd: docker build -t api .
        - cmd: docker build -t web ./frontend
        - ref: test api
        - cmd: kubectl apply -f k8s/
          retries: 3
        - cmd: kubectl rollout status deployment/api
          reviewRequired: true  # Pause for user confirmation

The executable environment variables and executable directory of the parent executable are inherited by the child executables.

Options:

  • failFast: Stop execution on first failure (default: true)
  • retries: Number of times to retry failed steps
  • reviewRequired: Pause for user confirmation

parallel - Concurrent Execution

Run multiple steps simultaneously:

yaml
executables:
  - verb: test
    name: all-suites
    parallel:
      maxThreads: 4  # Limit concurrent operations
      failFast: false  # Run all tests even if some fail
      execs:
        - cmd: npm run test:unit
        - cmd: npm run test:integration
        - cmd: npm run test:e2e
        - ref: lint code
          retries: 1

The executable environment variables and executable directory of the parent executable are inherited by the child executables.

Options:

  • maxThreads: Maximum concurrent operations (default: 5)
  • failFast: Stop all operations on first failure (default: true)
  • retries: Number of times to retry failed operations

launch - Open Applications

Open files, URLs, or applications:

yaml
executables:
  - verb: open
    name: workspace
    launch:
      uri: "$FLOW_WORKSPACE_PATH"
      app: "Visual Studio Code"

  - verb: open
    name: docs
    launch:
      uri: "https://flowexec.io"

  - verb: open
    name: note
    launch:
      uri: "./note.md"
      app: "Obsidian"

Options:

  • uri: File path or URL to open (required)
  • app: Specific application to use

request - HTTP Requests

Make HTTP requests to APIs:

yaml
executables:
  - verb: deploy
    name: webhook
    request:
      method: POST
      url: "https://api.example.com/deploy"
      headers:
        Authorization: "Bearer $API_TOKEN"
        Content-Type: "application/json"
      body: |
        {
          "environment": "$ENVIRONMENT",
          "version": "$VERSION"
        }
      timeout: 30s
      validStatusCodes: [200, 201]
      logResponse: true
      transformResponse: '"Deployed " + fromJSON(body)["status"]'
      responseFile:
        filename: "deploy-response.json"

Options:

  • method: HTTP method (GET, POST, PUT, PATCH, DELETE)
  • url: Request URL (required)
  • headers: Custom headers
  • body: Request body
  • timeout: Request timeout
  • validStatusCodes: Acceptable status codes
  • logResponse: Log response body
  • transformResponse: Expr expression to reshape the response before output or file save
  • responseFile: Save response to file

Transforming responses with transformResponse:

The transformResponse field is a single Expr expression evaluated after the request completes. Its result replaces the raw response body in any output or responseFile. The expression has access to:

VariableTypeDescription
bodystringRaw response body
codeintHTTP status code (e.g. 200, 404)
statusstringFull status line (e.g. "200 OK")
headersmap[string][]stringResponse headers

NOTE

headers is a map[string][]string — each name maps to a slice of values. Access the first value with headers["Content-Type"][0], not headers["Content-Type"].

Common patterns:

yaml
# Extract a field from a JSON body
transformResponse: fromJSON(body)["name"]

# Uppercase a status field
transformResponse: upper(fromJSON(body)["status"])

# Format an array as newline-separated output
transformResponse: join(map(fromJSON(body)["items"], #["name"]), "\n")

# Conditional with fallback
transformResponse: code == 200 ? fromJSON(body)["result"] : "error " + string(code) + ": " + body

# Let binding to avoid reparsing
transformResponse: let data = fromJSON(body); data["id"] + " — " + data["name"]

See the Expression Language guide for the full syntax reference.

render - Dynamic Documentation

Process a template file and display its output — useful for status dashboards, reports, and any dynamically-generated text:

yaml
executables:
  - verb: show
    name: status
    render:
      templateFile: "status-template.md"
      templateDataFile: "status-data.json"

Options:

  • templateFile: Markdown template file (required)
  • templateDataFile: JSON/YAML data file for the data variable
  • dir: Working directory
  • params: Environment variable definitions (available as env["KEY"])

Available template variables:

VariableTypeDescription
envmap[string]stringParams and environment variables from the executable
dataanyParsed contents of templateDataFile (nil if not set)

data is typed based on the file content — a JSON object becomes a map, a JSON array becomes a slice. Access fields with bracket notation: data["key"] or data[0]["field"].

Template file example — given a status-data.json:

json
{"service": "api", "version": "2.1.0", "replicas": 3}

A status-template.md template:

markdown
# Deployment Status

Service: {{ data["service"] }}
Version: {{ data["version"] }}
Replicas: {{ string(data["replicas"]) }}
Environment: {{ env["DEPLOY_ENV"] }}

{{ if data["version"] != "" }}
Last deployed: {{ data["version"] }}
{{ end }}

The template syntax uses {{ expression }} delimiters where expressions are evaluated using the Expr language. See the Expression Language guide for syntax and built-ins.

Importing Executables

Generate executables from scripts, Makefiles, package.json scripts, or docker-compose services:

yaml
# In flowfile
imports:
  - "scripts/deploy.sh"
  - "scripts/build.bat"
  - "scripts/setup.ps1"
  - "Makefile"
  - "frontend/package.json"
  - "docker-compose.yaml"

All imported executables are automatically tagged with generated and their file type (e.g., docker-compose, makefile, package.json).

Script Files

Script files (.sh, .bat, .cmd, .ps1, .py) are imported as single executables with the script's filename as the name and exec as the default verb. Each script type is executed with its native interpreter:

ExtensionInterpreterPlatforms
.shBuilt-in POSIX shellAll (cross-platform)
.bat, .cmdcmd.exe /CWindows
.ps1pwsh or powershellAll (requires PowerShell)
.pyResolved Python (see Running Python)All (requires Python)

You can use special comments to override executable metadata. The comment syntax depends on the script type:

bash
#!/bin/bash
# f:name=production f:verb=deploy
# f:description="Deploy to production environment"
# f:tag=production f:tag=critical
# f:timeout=10m

echo "Deploying to production..."
kubectl apply -f k8s/
batch
@echo off
REM f:name=production f:verb=deploy
REM f:description="Deploy to production environment"
REM f:tag=production f:tag=critical
REM f:timeout=10m

echo Deploying to production...
kubectl apply -f k8s\
powershell
# f:name=production f:verb=deploy
# f:description="Deploy to production environment"
# f:tag=production f:tag=critical
# f:timeout=10m

Write-Host "Deploying to production..."
kubectl apply -f k8s/
python
#!/usr/bin/env python3
# f:name=production f:verb=deploy
# f:description="Deploy to production environment"
# f:tag=production f:tag=critical
# f:timeout=10m

import subprocess

print("Deploying to production...")
subprocess.run(["kubectl", "apply", "-f", "k8s/"], check=True)

See the generated configuration reference for more details.

Makefiles

Makefile targets are imported as executables with a verb and name that best represents the target.

makefile
# Makefile

# f:name=app f:verb=build f:description="Build the application"
build:
	go build -o bin/app ./cmd/app

# Run all tests
test:
	go test ./...

# f:visibility=internal
clean:
	rm -rf bin/

See the generated configuration reference for more details on overriding executable configuration.

Package.json Scripts

NPM scripts from package.json are imported as executables with a verb and name that best represents the script name.

json
{
  "scripts": {
    "build": "webpack --mode production",
    "test": "jest",
    "dev": "webpack-dev-server --mode development",
    "lint": "eslint src/"
  }
}

This creates executables like:

  • build - Runs the build script
  • test - Runs the test script
  • start dev - Runs the development server
  • lint - Runs the linter

Docker Compose Services

Docker Compose files are imported to create executables for managing services:

yaml
# docker-compose.yml
version: '3.8'
services:
  app:
    build: .
    ports:
      - "3000:3000"

  db:
    image: postgres:13
    environment:
      POSTGRES_DB: myapp

  redis:
    image: redis:6

This creates executables like:

  • start app - Start the app service
  • start db - Start the database service
  • start redis - Start the Redis service
  • start (alias: all, services) - Start all services
  • stop (alias: all, services) - Stop all services
  • build app - Build the app service (if build config exists)

Executable References

Reference other executables to build modular workflows:

yaml
executables:
  # Reusable components
  - verb: build
    name: api
    exec:
      cmd: docker build -t api .

  - verb: test
    name: api
    exec:
      cmd: npm test

  # Composite workflows
  - verb: deploy
    name: full
    serial:
      execs:
        - ref: build api
        - ref: test api
        - cmd: kubectl apply -f api.yaml

  # Cross-workspace references (requires public visibility)
  - verb: deploy
    name: with-monitoring
    serial:
      execs:
        - ref: deploy full
        - ref: trigger monitoring/slack:deployment-complete

Reference formats:

  • ref: build api - Current workspace/namespace
  • ref: build workspace/namespace:api - Full reference
  • ref: build workspace/api - Specific workspace
  • ref: build namespace:api - Specific namespace

Cross-workspace requirements:

  • Referenced executables must have visibility: public
  • Private, internal, and hidden executables cannot be cross-referenced

What's Next?

Now that you understand all executable types and options: