TypeScript Workflows Guide
Overview
n8n-as-code now supports TypeScript workflows as an alternative to JSON. TypeScript workflows offer:
- Better readability - Declarative syntax with decorators
- Type safety - IntelliSense and compile-time checks
- Code review friendly - Git diffs are easier to understand
- AI agent compatible - More natural for AI to read and generate
Workflow Format Comparison
JSON Format (Traditional)
{
"id": "abc123",
"name": "My Workflow",
"nodes": [
{
"id": "node1",
"name": "Start",
"type": "n8n-nodes-base.start",
"position": [250, 300],
"parameters": {}
},
{
"id": "node2",
"name": "HTTP Request",
"type": "n8n-nodes-base.httpRequest",
"position": [450, 300],
"parameters": {
"url": "https://api.example.com",
"method": "GET"
}
}
],
"connections": {
"Start": {
"main": [[{ "node": "HTTP Request", "type": "main", "index": 0 }]]
}
}
}
TypeScript Format (New)
import 'reflect-metadata';
@workflow({ id: 'abc123', name: 'My Workflow', active: false })
export class MyWorkflow {
@node({ type: 'n8n-nodes-base.start', position: [250, 300] })
Start = {};
@node({
type: 'n8n-nodes-base.httpRequest',
position: [450, 300]
})
HttpRequest = {
url: 'https://api.example.com',
method: 'GET'
};
@links()
connections = () => {
this.Start.out().to(this.HttpRequest);
};
}
Key Concepts
1. Workflow Decorator
The @workflow decorator defines workflow metadata:
@workflow({
id: 'workflow-id', // Required for existing workflows
name: 'My Workflow', // Workflow name
active: false // Activation status
})
export class MyWorkflow { }
2. Node Decorators
The @node decorator defines nodes. Property names become node names:
@node({
type: 'n8n-nodes-base.httpRequest', // Node type (required)
position: [450, 300] // Canvas position (optional)
})
HttpRequestNode = {
// Node parameters (specific to node type)
url: 'https://api.example.com',
method: 'GET'
};
Node Naming
- Property names are auto-converted to PascalCase
- Collisions are handled automatically (HttpRequest → HttpRequest1, HttpRequest2)
- Use descriptive names for better readability
3. Connection Links
The @links decorator defines connections using fluent API:
@links()
connections = () => {
// Simple connection: node1 → node2
this.Start.out().to(this.HttpRequest);
// Multiple outputs: node → [node2, node3]
this.Switch
.out(0).to(this.ProcessA)
.out(1).to(this.ProcessB);
// Named outputs (for multi-output nodes)
this.IfNode
.out('true').to(this.ProcessTrue)
.out('false').to(this.ProcessFalse);
};
Common Patterns
Basic Linear Workflow
@workflow({ name: 'Linear Flow', active: false })
export class LinearFlow {
@node({ type: 'n8n-nodes-base.start' })
Start = {};
@node({ type: 'n8n-nodes-base.httpRequest' })
FetchData = { url: 'https://api.example.com' };
@node({ type: 'n8n-nodes-base.code' })
ProcessData = {
mode: 'runOnceForAllItems',
jsCode: 'return items;'
};
@links()
connections = () => {
this.Start.out().to(this.FetchData);
this.FetchData.out().to(this.ProcessData);
};
}
Branching Workflow (Switch)
@workflow({ name: 'Branching Flow', active: false })
export class BranchingFlow {
@node({ type: 'n8n-nodes-base.start' })
Start = {};
@node({ type: 'n8n-nodes-base.switch' })
Switch = {
mode: 'rules',
rules: {
rules: [
{ operation: 'equals', value: 'A' },
{ operation: 'equals', value: 'B' }
]
}
};
@node({ type: 'n8n-nodes-base.code' })
ProcessA = { jsCode: 'return items;' };
@node({ type: 'n8n-nodes-base.code' })
ProcessB = { jsCode: 'return items;' };
@links()
connections = () => {
this.Start.out().to(this.Switch);
this.Switch
.out(0).to(this.ProcessA)
.out(1).to(this.ProcessB);
};
}
Error Handling Workflow
@workflow({ name: 'Error Handling', active: false })
export class ErrorHandling {
@node({ type: 'n8n-nodes-base.start' })
Start = {};
@node({ type: 'n8n-nodes-base.httpRequest' })
RiskyRequest = {
url: 'https://api.example.com',
continueOnFail: true
};
@node({ type: 'n8n-nodes-base.code' })
HandleSuccess = { jsCode: 'return items;' };
@node({ type: 'n8n-nodes-base.code' })
HandleError = { jsCode: 'return items;' };
@links()
connections = () => {
this.Start.out().to(this.RiskyRequest);
// Main output → success handler
this.RiskyRequest.out().to(this.HandleSuccess);
// Error output → error handler
this.RiskyRequest.out('error').to(this.HandleError);
};
}
CLI Commands
Convert Existing Workflows
Single file conversion:
# JSON → TypeScript
n8nac convert my-workflow.json
# TypeScript → JSON
n8nac convert my-workflow.workflow.ts
# Specify output path
n8nac convert my-workflow.json -o custom-name.workflow.ts
# Force overwrite
n8nac convert my-workflow.json --force
Batch conversion:
# Convert all JSON files in directory to TypeScript
n8nac convert-batch ./workflows --format typescript
# Convert all TypeScript files to JSON
n8nac convert-batch ./workflows --format json
# Force overwrite existing files
n8nac convert-batch ./workflows --format typescript --force
Validate Workflows
# Validate JSON workflow
n8nac-skills validate my-workflow.json
# Validate TypeScript workflow
n8nac-skills validate my-workflow.workflow.ts
# Strict mode (warnings as errors)
n8nac-skills validate my-workflow.workflow.ts --strict
Download Community Workflows
# Search workflows
n8nac-skills workflows search "email automation"
# Download as JSON (default)
n8nac-skills workflows install 123
# Download as TypeScript
n8nac-skills workflows install 123 --typescript
# Custom output path
n8nac-skills workflows install 123 -o my-workflow.workflow.ts --typescript
Migration Guide
Migrating from JSON to TypeScript
Option 1: Manual conversion via CLI
-
Pull your existing workflows (if not already local)
n8nac list # See what's available
n8nac pull --workflowsid <workflowId> # Pull each workflow you need -
Convert workflows to TypeScript
n8nac convert-batch ./workflows --format typescript -
Review and commit the TypeScript files
git add workflows/*.workflow.ts
git commit -m "Migrate workflows to TypeScript" -
(Optional) Remove old JSON files
rm workflows/*.json
Option 2: Gradual migration
- Keep both formats during transition
- Convert workflows one by one
- Sync package supports both formats simultaneously
Best Practices
-
Use descriptive property names - They become node names in n8n
// ✅ Good
FetchUserData = { url: '...' }
ProcessUserProfile = { jsCode: '...' }
// ❌ Avoid
Node1 = { url: '...' }
N2 = { jsCode: '...' } -
Group related nodes - Organize properties logically
// Triggers
@node({ type: 'n8n-nodes-base.webhook' })
WebhookTrigger = { path: '/webhook' };
// Data fetching
@node({ type: 'n8n-nodes-base.httpRequest' })
FetchData = { url: '...' };
// Processing
@node({ type: 'n8n-nodes-base.code' })
Transform = { jsCode: '...' }; -
Add comments - Document complex logic
// Switch based on user type: admin, user, guest
@node({ type: 'n8n-nodes-base.switch' })
UserTypeSwitch = {
mode: 'rules',
rules: { /* ... */ }
}; -
Version control - Commit TypeScript files to Git
git add workflows/*.workflow.ts
git commit -m "Add user onboarding workflow"
Troubleshooting
TypeScript Compilation Errors
Error: Cannot find module 'reflect-metadata'
# Install reflect-metadata
npm install reflect-metadata
Error: Experimental decorators warning
- This is normal - decorators are enabled in tsconfig.json
- The warning can be safely ignored
Validation Errors
"Failed to compile TypeScript workflow"
- Check syntax errors in your
.workflow.tsfile - Ensure all imports are present
- Validate decorator usage
"Node is missing required field"
- Check that node
typeis specified in@nodedecorator - Ensure node parameters match the node type schema
Sync Issues
Workflows not syncing
- Ensure file extension is
.workflow.ts(not just.ts) - Check that workflow has an
idin@workflowdecorator - Verify n8n connection with
n8nac list
Hash mismatches
- TypeScript files are compiled to JSON before hashing
- Formatting differences won't cause conflicts
- Recompile:
n8nac convert workflow.workflow.tsthen back
Advanced Topics
Custom Node Types
For custom nodes, import types if available:
@node({ type: 'n8n-nodes-custom.myNode' })
CustomNode = {
customParam: 'value'
};
Workflow Settings
Add workflow settings in the decorator:
@workflow({
name: 'My Workflow',
active: false,
settings: {
executionOrder: 'v1',
saveExecutionProgress: true,
saveManualExecutions: true
}
})
export class MyWorkflow { }
Position Layout
Omit positions for AI-generated workflows:
// n8n will auto-layout nodes
@node({ type: 'n8n-nodes-base.start' })
Start = {};
For manual layout:
// [x, y] coordinates on canvas
@node({ type: 'n8n-nodes-base.start', position: [250, 300] })
Start = {};
API Reference
The transformer package provides the following key components:
Decorators
@workflow(metadata)- Define workflow metadata (id, name, active, etc.)@node(config)- Define a workflow node with type and configuration@links()- Define connections between nodes
Core Classes
JsonToAstParser- Parse JSON workflows to AST representationAstToTypeScriptGenerator- Generate TypeScript code from ASTTypeScriptParser- Parse TypeScript workflows to ASTWorkflowBuilder- Build n8n JSON from AST representation
Support
- Documentation: https://etiennelescot.github.io/n8n-as-code/
- Issues: GitHub Issues
- Discussions: GitHub Discussions