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:
flow browse # Interactive multi-pane browser
flow browse --list # Simple list view
flow browse VERB ID # View specific executable detailsFilter executables by workspace, namespace, verb, or tag:
flow browse --workspace api --namespace v1 --verb deploy --tag production
flow browse --all --filter "database" # Search names and descriptionsExecutable Configuration
Basic Structure
Every executable needs a verb and optionally a name:
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:
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.pemParameter types:
secretRef: Reference to vault secretprompt: Interactive user inputtext: Static valueenvFile: Load environment variables from a file
Arguments (args)
Handle command-line arguments:
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.txtRun with arguments:
flow build container -- v1.2.3 --publish=true --registry=my-registry.comWARNING
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:
flow deploy app --param API_TOKEN=override --param ENVIRONMENT=stagingNOTE
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:
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 directoryf:tmp: Temporary directory (auto-cleaned)$VAR: Environment variable expansion
Executable Types
exec - Shell Commands
Run commands or scripts directly:
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 hiddenOptions:
cmd: Inline command to runfile: Script file to executeinterpreter: Which interpreter runscmd—sh(default) orpython(see below)logMode: How to format command outputcontainer: 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:
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:
exec:
file: scripts/analyze.pySetting 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:
| Order | Source |
|---|---|
| 1 | FLOW_PYTHON_BIN, if set |
| 2 | $VIRTUAL_ENV — an activated virtualenv |
| 3 | <workspace root>/.venv |
| 4 | python3 on the PATH |
| 5 | python 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 python3 — python3.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.
executables:
- verb: build
name: go-app
exec:
cmd: go build -o bin/app ./cmd/app
container:
image: golang:1.21-alpineThe 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:
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: hostinterpreter: python works with container too — combine them to get a pinned Python toolchain without installing it locally:
exec:
interpreter: python
cmd: |
import sys
print(sys.version)
container:
image: python:3.13-alpineNotes and limitations:
- By default flow overrides the image entrypoint with
shsocmdbehaves as a shell command on any image. Withinterpreter: pythonthe default entrypoint becomespython3instead. Setentrypoint: ""to use the image's ownENTRYPOINT— with Python that only works if the image'sENTRYPOINTis itself an interpreter. - Host interpreter discovery does not apply inside a container: the image's own
python3is used, andVIRTUAL_ENV,PYTHONPATH,PYTHONHOME, andFLOW_PYTHON_BINare dropped from the container environment because those host paths mean nothing inside it. Install dependencies in the image, or mount them withvolumes. - On Linux, flow runs as your host user by default so mounted files are not root-owned. Set
user: rootto opt out. .bat,.cmd, and.ps1files are not supported withcontainer.containerapplies toexecexecutables only; inlinecmdsteps insideserial/paralleldo not inherit it — reference a container-backed executable instead. (interpreteris available on those steps — see below.)outputFiledestinations 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/foldersby default, sodir: f:tmpmay 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:
executables:
- verb: run
name: pipeline
serial:
execs:
- cmd: ./fetch-data.sh
- cmd: |
import json
print(json.load(open("data.json"))["total"])
interpreter: pythonA 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:
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 confirmationThe 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 stepsreviewRequired: Pause for user confirmation
parallel - Concurrent Execution
Run multiple steps simultaneously:
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: 1The 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:
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:
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 headersbody: Request bodytimeout: Request timeoutvalidStatusCodes: Acceptable status codeslogResponse: Log response bodytransformResponse: Expr expression to reshape the response before output or file saveresponseFile: 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:
| Variable | Type | Description |
|---|---|---|
body | string | Raw response body |
code | int | HTTP status code (e.g. 200, 404) |
status | string | Full status line (e.g. "200 OK") |
headers | map[string][]string | Response 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:
# 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:
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 thedatavariabledir: Working directoryparams: Environment variable definitions (available asenv["KEY"])
Available template variables:
| Variable | Type | Description |
|---|---|---|
env | map[string]string | Params and environment variables from the executable |
data | any | Parsed 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:
{"service": "api", "version": "2.1.0", "replicas": 3}A status-template.md template:
# 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:
# 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:
| Extension | Interpreter | Platforms |
|---|---|---|
.sh | Built-in POSIX shell | All (cross-platform) |
.bat, .cmd | cmd.exe /C | Windows |
.ps1 | pwsh or powershell | All (requires PowerShell) |
.py | Resolved Python (see Running Python) | All (requires Python) |
You can use special comments to override executable metadata. The comment syntax depends on the script type:
#!/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/@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\# 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/#!/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
# 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.
{
"scripts": {
"build": "webpack --mode production",
"test": "jest",
"dev": "webpack-dev-server --mode development",
"lint": "eslint src/"
}
}This creates executables like:
build- Runs the build scripttest- Runs the test scriptstart dev- Runs the development serverlint- Runs the linter
Docker Compose Services
Docker Compose files are imported to create executables for managing services:
# docker-compose.yml
version: '3.8'
services:
app:
build: .
ports:
- "3000:3000"
db:
image: postgres:13
environment:
POSTGRES_DB: myapp
redis:
image: redis:6This creates executables like:
start app- Start the app servicestart db- Start the database servicestart redis- Start the Redis servicestart(alias: all, services) - Start all servicesstop(alias: all, services) - Stop all servicesbuild app- Build the app service (if build config exists)
Executable References
Reference other executables to build modular workflows:
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-completeReference formats:
ref: build api- Current workspace/namespaceref: build workspace/namespace:api- Full referenceref: build workspace/api- Specific workspaceref: 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:
- Build complex workflows → Advanced workflows
- Secure your automation → Working with secrets
- Generate project templates → Templates & code generation

