Skip to main content

Code Externalization

Store Python/JavaScript code in separate files instead of inline in workflow JSON, making code easier to version control, review, and maintain.

Overview

Instead of storing code inline in workflow JSON:

{
"nodes": [
{
"parameters": {
"pythonCode": "def process(data):\n return data.upper()\n\nresult = process(input)"
}
}
]
}

Store it in separate files:

Workflow JSON:

{
"nodes": [
{
"parameters": {
"pythonCode": "@@n8n-gitops:include scripts/my-workflow/process_data.py"
}
}
]
}

Script file (n8n/scripts/my-workflow/process_data.py):

def process(data):
return data.upper()

result = process(input)

Benefits

Better version control: See actual code diffs, not escaped JSON strings ✅ Code review: Review Python/JavaScript in native format ✅ IDE support: Syntax highlighting, linting, autocomplete ✅ Reusability: Share code between workflows ✅ Testing: Test code independently of workflows

Include Directive Syntax

Basic Syntax

@@n8n-gitops:include <path>

Where <path> is relative to n8n/ directory and must be under scripts/.

Examples

@@n8n-gitops:include scripts/payments/retry.py
@@n8n-gitops:include scripts/utilities/format.js
@@n8n-gitops:include scripts/my-workflow/helper.py

Path Rules

Allowed:

  • scripts/payments/process.py
  • scripts/my-workflow/transform.js
  • scripts/utils/helpers/format.py

Not allowed:

  • ../secrets.txt (path traversal)
  • /etc/passwd (absolute path)
  • config/api-keys.json (not under scripts/)

Exporting with Externalization

Externalization is controlled by the externalize_code flag in n8n/manifests/workflows.yaml (default: true from create-project).

# n8n/manifests/workflows.yaml
externalize_code: true # extract code to n8n/scripts/ and add include directives

Steps:

  1. Set externalize_code in the manifest to your desired mode.
  2. Run n8n-gitops export (no extra flags needed).
  3. Review generated scripts in n8n/scripts/<workflow-name>/ and include directives in workflow JSON.

Supported Code Fields

Code is extracted from these node parameter fields:

FieldExtensionExample Node Type
pythonCode.pyCode (Python)
jsCode.jsCode (JavaScript)
code.jsFunction, Function Item
functionCode.jsFunction (legacy)

File Naming

Format: {node-name}_{field-name}.{ext}

Examples:

  • Node "Process Data" with pythonCodeProcess_Data.py
  • Node "Transform" with jsCodeTransform.js
  • Node "Calculate-Price" with codeCalculate_Price_code.js

Directory Structure

my-n8n-project/
├── n8n/
│ ├── workflows/
│ │ ├── payment-processing.json
│ │ └── data-sync.json
│ ├── scripts/
│ │ ├── payment-processing/
│ │ │ ├── validate_payment.py
│ │ │ ├── retry_logic.py
│ │ │ └── format_response.js
│ │ └── data-sync/
│ │ └── transform_data.py
│ └── manifests/
│ └── workflows.yaml

Switching Between Modes

From Inline to Externalized

  1. Set externalize_code: true in n8n/manifests/workflows.yaml.
  2. Run n8n-gitops export.

Result: script files are created in n8n/scripts/ and workflow JSON uses include directives.

From Externalized to Inline

  1. Set externalize_code: false in n8n/manifests/workflows.yaml.
  2. Run n8n-gitops export.

Result: script files under n8n/scripts/ are removed and workflow JSON contains inline code.

Deployment with Includes

During deployment, include directives are automatically resolved:

n8n-gitops deploy

The deployment process:

  1. Reads workflow JSON
  2. Detects @@n8n-gitops:include directives
  3. Loads referenced script files
  4. Replaces directives with actual code
  5. Deploys to n8n with inline code

Important: n8n always receives workflows with inline code. Include directives are a Git-only feature.

Example Workflow

1. Export with Externalization

Ensure externalize_code: true is set in n8n/manifests/workflows.yaml, then run:

n8n-gitops export

2. Edit Script File

Edit n8n/scripts/payment-processing/validate_payment.py:

def validate_payment(amount, currency):
"""Validate payment parameters."""
if amount <= 0:
raise ValueError("Amount must be positive")

if currency not in ["USD", "EUR", "GBP"]:
raise ValueError(f"Unsupported currency: {currency}")

return {"valid": True, "amount": amount, "currency": currency}

# Main execution
payment = items[0].json
result = validate_payment(payment["amount"], payment["currency"])

3. Commit Changes

git add n8n/scripts/payment-processing/validate_payment.py
git commit -m "Improve payment validation logic"
git tag v1.1.0

4. Deploy

n8n-gitops deploy --git-ref v1.1.0

The updated code is automatically injected into the workflow during deployment.

Workflow JSON Example

Before Externalization

{
"name": "Payment Processing",
"nodes": [
{
"name": "Validate Payment",
"type": "n8n-nodes-base.code",
"parameters": {
"pythonCode": "def validate(amount):\n if amount <= 0:\n raise ValueError('Invalid')\n return amount\n\nresult = validate(items[0].json.amount)"
}
},
{
"name": "Format Response",
"type": "n8n-nodes-base.code",
"parameters": {
"jsCode": "const format = (data) => {\n return { status: 'ok', data };\n};\n\nreturn format(items[0].json);"
}
}
]
}

After Externalization

Workflow JSON:

{
"name": "Payment Processing",
"nodes": [
{
"name": "Validate Payment",
"type": "n8n-nodes-base.code",
"parameters": {
"pythonCode": "@@n8n-gitops:include scripts/payment-processing/Validate_Payment.py"
}
},
{
"name": "Format Response",
"type": "n8n-nodes-base.code",
"parameters": {
"jsCode": "@@n8n-gitops:include scripts/payment-processing/Format_Response.js"
}
}
]
}

n8n/scripts/payment-processing/Validate_Payment.py:

def validate(amount):
if amount <= 0:
raise ValueError('Invalid')
return amount

result = validate(items[0].json.amount)

n8n/scripts/payment-processing/Format_Response.js:

const format = (data) => {
return { status: 'ok', data };
};

return format(items[0].json);

Best Practices

1. Always Use Externalization in Git

# Set externalize_code: true (default) and export
n8n-gitops export

This gives you the best version control experience.

2. Use Descriptive Node Names

Good:

  • "Validate Payment Data"
  • "Transform User Input"
  • "Format API Response"

Bad:

  • "Code"
  • "Python"
  • "Node 1"

Node names become part of filenames, so make them descriptive.

3. Keep Scripts Directory Clean

Don't manually create files in n8n/scripts/. Let n8n-gitops export generate them based on externalize_code in the manifest.

4. Add Comments

Since code is in separate files, add proper comments:

"""
Validate payment data before processing.

Checks:
- Amount is positive
- Currency is supported
- Required fields are present
"""
def validate_payment(data):
# Implementation...

5. Test Scripts Independently

You can test extracted scripts with standard Python/JavaScript tools:

# Python
python3 n8n/scripts/payment/validate.py

# JavaScript
node n8n/scripts/payment/format.js

Validation

Validate include directives before deployment:

n8n-gitops validate

This checks:

  • Include paths are valid
  • Referenced files exist
  • Paths don't escape scripts/ directory
  • No path traversal attempts

See Also