Habit Schema
Workflows in Habits define automation sequences by connecting nodes that execute in order.
Full Schema
yaml
$schema: http://json-schema.org/draft-07/schema#
definitions:
Record<string,any>:
type: object
# Logging configuration for workflow-level or stack-level logging
LogLevel:
type: string
enum:
- trace
- debug
- info
- warn
- error
- fatal
- none
description: |
Log level for filtering messages.
trace: Most verbose, for detailed debugging
debug: Debugging information
info: General information (default)
warn: Warnings, potential issues
error: Errors, failures
fatal: Critical errors, system failures
none: Disable all logging
LoggingConfig:
type: object
description: |
Logging configuration. Can be specified in habit.yaml (per-workflow) or stack.yaml (global).
Environment variables override all config (HABITS_LOG_LEVEL, HABITS_LOG_OUTPUT, etc.)
properties:
level:
$ref: "#/definitions/LogLevel"
description: Default log level for all loggers
default: info
outputs:
type: array
items:
type: string
enum:
- console
- file
- json
description: Output destinations (console, file, json)
default: [console]
file:
type: object
description: File output configuration (only used if 'file' is in outputs)
properties:
path:
type: string
description: Path to log file
default: ./logs/habits.log
maxSize:
type: string
description: Maximum file size before rotation (e.g., '10mb', '1gb')
default: 10mb
maxFiles:
type: integer
description: Maximum number of rotated files to keep
default: 5
minimum: 1
format:
type: string
enum:
- text
- json
description: Output format for console/file
default: text
colorize:
type: boolean
description: Enable ANSI colors in console output (auto-detected if not specified)
bitOverrides:
type: object
description: |
Per-bit log level overrides. Keys are bit names (e.g., 'bit-http', 'bit-openai').
Values are log levels.
additionalProperties:
$ref: "#/definitions/LogLevel"
WorkflowEdge:
type: object
properties:
id:
type: string
source:
type: string
sourceHandle:
type: string
target:
type: string
targetHandle:
type: string
WorkflowNode:
type: object
properties:
id:
type: string
type:
type: string
enum:
- action
- activepieces
- n8n
- trigger
- script
# Future/WIP type - used in conceptual docs examples
- bit # Custom bits (see roadmap/bits.md) - includes declarative, moderation, and other bit subtypes
position:
type: object
properties:
x:
type: number
y:
type: number
data:
type: object
properties:
label:
type: string
framework:
type: string
enum:
- activepieces
- n8n
- script
# Future/WIP framework - used in conceptual docs examples
- bits # Custom bits framework (see roadmap/bits.md)
module:
type: string
operation:
type: string
resource:
type: string
source:
type: string
description: Module source - 'local', 'npm', 'github', or 'link'
enum:
- local
- npm
- github
- link
registry:
type: string
description: |
Custom npm registry URL for 'npm' source modules.
If not provided, uses HABITS_NPM_REGISTRY_URL environment variable,
or defaults to https://registry.npmjs.org.
Useful for private registries like Verdaccio.
isTrigger:
type: boolean
description: Marks this node as a trigger/entry point for the workflow
inputs:
type: array
items:
type: string
outputs:
type: array
items:
type: string
inputTransforms:
$ref: "#/definitions/Record<string,any>"
credentials:
$ref: "#/definitions/Record<string,any>"
params:
type: object
additionalProperties: true
properties:
type:
type: string
description: Type parameter - 'script'
language:
type: string
enum:
- bash
- deno
- go
- python3
- sql
- typescript
description: Script language for script framework nodes
script:
type: string
description: The script code for script framework nodes
stopAfterIf:
type: object
properties:
expr:
type: string
skipIfStopped:
type: boolean
type: object
properties:
id:
type: string
name:
type: string
description:
type: string
version:
type: string
nodes:
type: array
items:
$ref: "#/definitions/WorkflowNode"
edges:
type: array
items:
$ref: "#/definitions/WorkflowEdge"
output:
type: object
description: Output mapping for workflow results
additionalProperties:
type: string
logging:
$ref: "#/definitions/LoggingConfig"
description: |
Per-workflow logging configuration (optional).
Overridden by stack.yaml logging config, which is overridden by environment variables.Workflow Structure
| Field | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Unique identifier for the workflow |
name | string | Yes | Human-readable name |
description | string | No | Workflow description |
inputs | object | No | Input parameters with types and defaults |
nodes | array | Yes | List of node definitions |
edges | array | Yes | Node connections defining execution flow |
output | object | No | Output mapping for workflow results |
Nodes
Each node represents a single operation in your workflow.
Node Fields
| Field | Type | Description |
|---|---|---|
id | string | Unique identifier for the node |
type | string | activepieces, n8n, script, or bit |
position | object | Optional { x, y } coordinates for visual layout |
data | object | Node configuration (see below) |
Node Data Fields
| Field | Type | Description |
|---|---|---|
label | string | Display name for the node |
framework | string | activepieces, n8n, script, or bits |
module | string | Module/package name (e.g., @activepieces/piece-openai) |
operation | string | Operation to execute |
resource | string | Resource type (n8n nodes) |
source | string | Module source (npm, inline) |
isTrigger | boolean | Marks node as workflow entry point |
params | object | Operation parameters |
credentials | object | Authentication credentials |
inputTransforms | object | Input data transformations |
stopAfterIf | object | Conditional stop with expr and skipIfStopped |
Script Params
For type: script nodes:
| Field | Type | Description |
|---|---|---|
type | string | Set to "script" |
language | string | bash, deno, go, python3, sql, or typescript |
script | string | The script code to execute |
Edges
Edges define how data flows between nodes.
| Field | Type | Required | Description |
|---|---|---|---|
id | string | No | Unique identifier for the edge |
source | string | Yes | ID of the source node |
target | string | Yes | ID of the target node |
sourceHandle | string | No | Output handle on source node |
targetHandle | string | No | Input handle on target node |
Data References
Reference data from other nodes using template syntax:
| Pattern | Description |
|---|---|
{{<nodeId>}} | Full output from a specific node |
{{<nodeId>.<path>}} | Nested property from node output (JSON) |
{{habits.env.<VAR>}} | Environment variable |
{{habits.input.<field>}} | Runtime input parameter |
{{habits.header.<header>}} | Request header |
{{habits.cookies.<cookie>}} | Request cookie |
Stack Configuration
The stack.yaml file defines workflows and server settings:
| Field | Type | Description |
|---|---|---|
version | string | Config version |
workflows | array | Workflow paths or inline definitions |
server.port | number | Server port (default: 3000) |
server.host | string | Server host (default: 0.0.0.0) |
logging | object | Logging configuration |
Examples
See complete working examples:
yaml
id: text-to-voice-to-s3
name: text-to-voice-to-s3
nodes:
- id: generate-text
type: activepieces
data:
framework: activepieces
module: '@activepieces/piece-openai'
operation: ask_chatgpt
source: npm
credentials:
apiKey: '{{habits.env.OPENAI_API_KEY}}'
params:
prompt: 'Write a short 2-sentence motivational quote related to: {{habits.input.prompt}}'
model: gpt-4o-mini
- id: text-to-speech
type: n8n
data:
framework: n8n
module: n8n-nodes-elevenlabs
operation: text-to-speech
source: npm
credentials:
elevenLabsApi:
xiApiKey: '{{habits.env.ELEVENLABS_API_KEY}}'
params:
resource: speech
text: '{{generate-text}}'
voice_id: 21m00Tcm4TlvDq8ikWAM
- id: save-locally
type: script
data:
label: Save Audio Locally
framework: script
source: inline
params:
audio: '{{text-to-speech.result.0.base64}}'
language: deno
script: |
export async function main(audio: string | ArrayBuffer) {
const outputDir = '/tmp/habits-audio';
await Deno.mkdir(outputDir, { recursive: true });
const filename = `audio-${Date.now()}.mp3`;
const filepath = `${outputDir}/${filename}`;
// Use Buffer for base64 decoding (works in Node.js environment)
const buffer = typeof audio === 'string'
? Buffer.from(audio, 'base64')
: new Uint8Array(audio);
await Deno.writeFile(filepath, buffer);
const stats = await Deno.stat(filepath);
return {
success: true,
filepath,
filename,
size: stats.size,
modifiedAt: stats.mtime?.toISOString()
};
}
edges:
- source: generate-text
target: text-to-speech
- source: text-to-speech
target: save-locallyyaml
version: "1.0"
workflows:
- id: text-to-voice-to-s3
path: ./habit.yaml
enabled: true
server:
port: 13000
host: "0.0.0.0"
frontend: ./frontend
logging:
level: info # trace, debug, info, warn, error, fatal, none
outputs: [console] # console, file, json
format: text # text or json
colorize: trueyaml
id: habit-code
name: Habit Code Demo
description: A habit demonstrating if conditions, loops, and string utilities using pure script nodes (Deno)
# This habit demonstrates using Deno scripts for:
# 1. If conditions - Check if data is valid
# 2. Loops - Iterate over items and transform them
# 3. String utils - Split, join, uppercase, lowercase operations
nodes:
# Node 1: Create test data
- id: create-data
type: script
data:
framework: script
source: inline
label: Create Test Data
params:
type: script
language: deno
script: |
export async function main() {
console.log("📝 Creating test data");
return {
name: "habits demo",
items: "apple,banana,cherry,date",
count: 4,
isValid: true
};
}
# Node 2: If condition - Check if data is valid
- id: check-valid
type: script
data:
framework: script
source: inline
label: If Condition Check
params:
isValid: "{{create-data.result.isValid}}"
count: "{{create-data.result.count}}"
type: script
language: deno
script: |
export async function main(isValid: boolean, count: number) {
console.log("🔀 Running if condition check");
// IF condition: check if data is valid and count > 0
if (isValid && count > 0) {
return {
passed: true,
message: "Data validation passed",
branch: "trueBranch"
};
} else {
return {
passed: false,
message: "Data validation failed",
branch: "falseBranch"
};
}
}
# Node 3: String utils - Transform text
- id: string-utils
type: script
data:
framework: script
source: inline
label: String Utilities
params:
text: "{{create-data.result.name}}"
type: script
language: deno
script: |
export async function main(text: string) {
console.log("📝 Running string utilities");
return {
original: text,
uppercase: text.toUpperCase(),
lowercase: text.toLowerCase(),
titleCase: text.split(' ').map(word =>
word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()
).join(' '),
reversed: text.split('').reverse().join(''),
length: text.length,
words: text.split(' ').length
};
}
# Node 4: Loop - Split and iterate over items
- id: loop-items
type: script
data:
framework: script
source: inline
label: Loop Over Items
params:
itemsString: "{{create-data.result.items}}"
type: script
language: deno
script: |
export async function main(itemsString: string) {
console.log("🔄 Running loop over items");
// Split the string into array
const items = itemsString.split(',').map(item => item.trim());
// Loop through items and transform each
const transformedItems = [];
for (let i = 0; i < items.length; i++) {
const item = items[i];
transformedItems.push({
index: i,
original: item,
uppercase: item.toUpperCase(),
length: item.length,
startsWithVowel: /^[aeiou]/i.test(item)
});
}
// Aggregate results
const vowelStarters = transformedItems.filter(item => item.startsWithVowel);
return {
totalItems: items.length,
items: transformedItems,
joinedUppercase: transformedItems.map(i => i.uppercase).join(' | '),
vowelStarters: vowelStarters.map(i => i.original),
vowelStarterCount: vowelStarters.length
};
}
# Node 5: Final summary combining all results
- id: summary
type: script
data:
framework: script
source: inline
label: Generate Summary
params:
validation: "{{check-valid.result}}"
stringOps: "{{string-utils.result}}"
loopResult: "{{loop-items.result}}"
type: script
language: deno
script: |
export async function main(validation: any, stringOps: any, loopResult: any) {
console.log("📊 Generating final summary");
return {
validationPassed: validation.passed,
validationMessage: validation.message,
transformedName: stringOps.titleCase,
allStringTransforms: stringOps,
totalItemsProcessed: loopResult.totalItems,
itemsWithVowels: loopResult.vowelStarters,
processedItems: loopResult.items,
processedAt: new Date().toISOString(),
summary: `Processed ${loopResult.totalItems} items. Validation: ${validation.passed ? 'PASSED' : 'FAILED'}. Name transformed to: ${stringOps.titleCase}`
};
}
edges:
- source: create-data
target: check-valid
- source: create-data
target: string-utils
- source: create-data
target: loop-items
- source: check-valid
target: summary
- source: string-utils
target: summary
- source: loop-items
target: summary
output:
summary: "{{summary.result}}"
validation: "{{check-valid.result}}"
stringOperations: "{{string-utils.result}}"
loopResults: "{{loop-items.result}}"Next Steps
- Creating Habits - Build workflows visually or as code
- Running Automations - Execute and monitor workflows
- Showcase - Browse complete showcase
