The moment your visual workflow hits a wall, the Code node becomes your escape hatch. You need to calculate the difference between two dates in business days. Or transform a nested API response into a flat structure. Or aggregate hundreds of items into a single summary. The built-in nodes can’t help, but a few lines of code can.
The Code node is the most powerful and most misunderstood node in n8n. It lets you write JavaScript or Python directly inside your workflow, giving you unlimited transformation capabilities. But that power comes with responsibility: wrong return structures break workflows silently, type mismatches cause mysterious failures, and the difference between JavaScript and Python syntax trips up even experienced developers.
The Custom Logic Problem
n8n provides hundreds of pre-built nodes for common tasks. But automation requirements are infinite. No matter how many nodes exist, some transformation will always fall outside what visual configuration can handle.
Consider these scenarios that demand code:
- Calculating a hash or checksum for data validation
- Grouping items by multiple dynamic properties
- Transforming deeply nested JSON into a different structure
- Performing complex conditional logic across multiple fields
- Aggregating data with custom business rules
The Edit Fields node handles simple mappings. The If node handles branching. But when you need algorithmic control over your data, the Code node is the answer.
What You’ll Learn
- When to use the Code node versus simpler alternatives
- The critical difference between JavaScript and Python modes
- How the two run modes work and when to use each
- The return structure rule that causes 90% of Code node errors
- All built-in methods and variables for both languages
- Common errors and exactly how to fix them
- Debugging techniques that save hours of frustration
- Real-world examples with both JavaScript and Python
When to Use the Code Node
Before writing any code, confirm the Code node is actually necessary. Overusing it creates maintenance burden and hides logic that could be visible in the workflow canvas.
| Scenario | Use Code Node? | Better Alternative |
|---|---|---|
| Simple field renaming | No | Edit Fields node |
| Filter items by one condition | No | Filter node or If node |
| Format a single date | No | Expression with Luxon |
| Complex multi-field transformation | Yes | Code gives full control |
| Aggregate all items into summary | Yes | Reduce operations need code |
| Group items by dynamic property | Yes | Code is the only option |
| Parse or generate custom formats | Yes | String manipulation needs code |
| Business logic with multiple conditions | Yes | Code is cleaner than nested Ifs |
| Sort by multiple criteria | Depends | Sort node handles simple cases |
| API response restructuring | Usually yes | Code handles nested data better |
Rule of thumb: If you can accomplish the task with expressions in 1-2 nodes, skip the Code node. If you need loops, aggregations, or complex conditionals, the Code node is your tool.
For copy-paste JavaScript recipes covering common transformations, see our 25 JavaScript recipes for the Code node.
JavaScript vs Python: Choosing Your Language
The Code node supports both JavaScript and Python. Your choice affects performance, available features, and syntax patterns.
| Factor | JavaScript | Python |
|---|---|---|
| Performance | Faster execution | Slower (compilation overhead) |
| Syntax prefix | $ (e.g., $input, $json) | _ (e.g., _input, _item) |
| External modules | npm packages (self-hosted) | Limited support |
| Community examples | More abundant | Growing rapidly |
| Date handling | Luxon built-in | Luxon via _now |
| Familiarity | Web developers | Data scientists, analysts |
| Stability | Mature | Stable since n8n v2 |
When to Choose JavaScript
- You have JavaScript experience
- Performance matters for large datasets
- You need external npm packages
- You want maximum community examples and support
- Complex async operations (though Code node is synchronous)
When to Choose Python
- You’re more comfortable with Python syntax
- Your team uses Python elsewhere
- The transformation logic is straightforward
- You prefer Python’s cleaner syntax for data manipulation
Important: Python mode uses Pyodide to run Python in the browser. This adds compilation overhead, making Python noticeably slower than JavaScript for the same operation. For performance-critical workflows processing thousands of items, JavaScript is the better choice.
Code Node Fundamentals
Before diving into language specifics, understand the core concepts that apply to both JavaScript and Python.
Adding the Code Node
- Click + to add a node
- Search for “Code”
- Select the Code node
- Choose your language (JavaScript or Python)
The Two Run Modes
This single setting causes more confusion than anything else in the Code node.
| Mode | Execution | Data Access | Best For |
|---|---|---|---|
| Run Once for All Items | Code runs once, receives all items | $input.all() / _input.all() | Aggregations, batch processing, when items interact |
| Run Once for Each Item | Code runs separately per item | $json / _item | Simple per-item transformations |
Default is “Run Once for All Items.” Most transformations use this mode because it provides more control.
All Items mode example: You have 100 orders and need to calculate the total revenue. The code runs once, loops through all orders, and returns a single sum.
Each Item mode example: You have 100 orders and need to add a “processed” timestamp to each. The code runs 100 times, once per order, adding the timestamp individually.
The Golden Return Structure Rule
Every Code node must return data in a specific format. This rule causes the majority of Code node errors.
JavaScript:
return [
{
json: {
// your data here
}
}
];
Python:
return [
{
"json": {
# your data here
}
}
]
The structure is:
- An outer array containing items
- Each item is an object with a
jsonproperty - The
jsonproperty contains your actual data
Forget this structure and your workflow breaks with cryptic errors like “Code doesn’t return items properly.”
JavaScript Deep Dive
JavaScript is the native language for n8n’s Code node, offering the best performance and most features.
Accessing Input Data
| Method | Returns | Use Case |
|---|---|---|
$input.all() | Array of all items | Processing multiple items |
$input.first() | First item only | When you need just one |
$input.last() | Last item only | Getting the final item |
$json | Current item’s JSON (Each Item mode) | Per-item transformations |
$('NodeName').all() | All items from specific node | Accessing earlier node data |
$('NodeName').first() | First item from specific node | Getting one item from a node |
Example: All Items mode
// Get all incoming items as an array
const items = $input.all();
// Transform each item using map()
// map() creates a new array by applying a function to each element
return items.map(item => ({
// Each returned object must have a 'json' wrapper
json: {
// Access original data via item.json.fieldName
name: item.json.name,
// Add new fields as needed
processed: true
}
}));
Example: Each Item mode
// In "Each Item" mode, $json gives direct access to current item's data
// No need to call $input.all() - the code runs once per item automatically
return [{
json: {
// Access fields directly from $json
name: $json.name,
processed: true
}
}];
Referencing Other Nodes
Access data from any previous node using its exact name:
// Reference another node by its exact name (case-sensitive, include spaces)
// .all() returns an array of all items from that node
const httpData = $('HTTP Request').all();
// .first() returns only the first item
// .json accesses the actual data inside the item
const firstItem = $('HTTP Request').first().json;
// Chain together to get a specific field value
const userId = $('HTTP Request').first().json.userId;
Common mistake: The node name must match exactly, including spaces and capitalization. Copy the name from the node’s header bar to avoid typos.
Built-in Methods and Variables
n8n provides several built-in helpers:
| Method/Variable | Description | Example |
|---|---|---|
$now | Current timestamp as Luxon DateTime | $now.toFormat('yyyy-MM-dd') |
$today | Today at midnight as Luxon DateTime | $today.plus({ days: 7 }) |
$jmespath() | Query JSON with JMESPath | $jmespath(data, 'items[*].name') |
$if() | Conditional value | $if(condition, trueVal, falseVal) |
$execution | Current execution metadata | $execution.id |
$workflow | Current workflow metadata | $workflow.name |
$vars | Workflow variables | $vars.apiKey |
DateTime | Luxon DateTime class | DateTime.now() |
Date handling with Luxon:
// $now is a Luxon DateTime object representing the current moment
// toFormat() converts it to a string in your desired format
const today = $now.toFormat('yyyy-MM-dd'); // Output: "2025-12-16"
// plus() adds time to a date; returns a new DateTime object
// toISO() outputs in ISO 8601 format (great for APIs)
const nextWeek = $now.plus({ days: 7 }).toISO();
// DateTime.fromISO() parses an ISO date string into a DateTime object
// Then format it however you need
const parsed = DateTime.fromISO('2025-01-15').toFormat('MMMM d, yyyy'); // "January 15, 2025"
// diff() calculates the difference between two dates
// Second argument specifies the unit ('days', 'hours', 'months', etc.)
const start = DateTime.fromISO(item.json.startDate);
const end = DateTime.fromISO(item.json.endDate);
const days = end.diff(start, 'days').days; // Number of days between dates
External npm Modules
If you self-host n8n, you can enable external npm packages by setting an environment variable:
NODE_FUNCTION_ALLOW_EXTERNAL=lodash,axios,moment
Then in your Code node:
// Use require() to import external modules (NOT import/export syntax)
const _ = require('lodash');
// Now you can use lodash functions
// groupBy creates an object with keys from each item's 'category' field
const grouped = _.groupBy(items, 'category');
n8n Cloud users: External modules are restricted. Most transformations can be accomplished with built-in JavaScript methods.
See the official n8n Code node documentation for the complete list of allowed modules.
Error Handling
Wrap risky operations in try-catch to prevent workflow failures:
// Get all input items
const items = $input.all();
// Process each item, handling errors individually
return items.map(item => {
try {
// Code inside try block might throw an error
// JSON.parse() fails if rawData isn't valid JSON
const parsed = JSON.parse(item.json.rawData);
// If successful, return the parsed data
return {
json: {
success: true,
data: parsed
}
};
} catch (error) {
// If an error occurs, this block runs instead
// Log the error to browser console for debugging
console.log('Parse error:', error.message);
// Return an error object instead of crashing
// This lets the workflow continue with other items
return {
json: {
success: false,
error: error.message,
originalData: item.json.rawData // Keep original for debugging
}
};
}
});
This pattern lets the workflow continue even when individual items fail processing.
Python Deep Dive
Python mode brings familiar syntax for those who prefer it, with some important differences from JavaScript.
Syntax Differences
Python uses underscore prefix (_) instead of dollar sign ($):
| JavaScript | Python |
|---|---|
$input.all() | _input.all() |
$json | _item |
$now | _now |
$('Node').all() | _("Node").all() |
Accessing Input Data
All Items mode:
# Get all incoming items as a list
# Note: underscore prefix (_) instead of dollar sign ($)
items = _input.all()
# Build the result list manually
result = []
# Loop through each item
for item in items:
# Append transformed data to result
# Must use "json" key (as string) in Python
result.append({
"json": {
# Access original fields via item.json.fieldName
"name": item.json.name,
# Python uses True/False (capitalized), not true/false
"processed": True
}
})
# Return the complete result array
return result
Each Item mode:
# In "Each Item" mode, _item gives direct access to current item's data
# Code runs automatically for each item - no loop needed
return [{
"json": {
# Access fields directly from _item
"name": _item.name,
"processed": True
}
}]
The JsProxy Challenge
When accessing n8n data in Python, you often encounter JsProxy objects instead of native Python types. This is because Python runs via Pyodide in the browser, and JavaScript objects aren’t automatically converted.
Symptom: Printing data shows [object Object] or operations fail unexpectedly.
Solution: Use .to_py() to convert JsProxy to Python dictionaries:
# Get all items from input
items = _input.all()
for item in items:
# item.json is a JsProxy object (JavaScript object in Python)
# JsProxy doesn't work with Python dict methods like .keys() or .get()
# Convert JsProxy to native Python dictionary using .to_py()
data = item.json.to_py()
# Now 'data' is a regular Python dict - all Python methods work
print(data) # Shows actual data instead of [object Object]
When you need .to_py():
- Iterating over object properties
- Using Python dict methods
- Serializing to JSON string
- Any operation expecting a native Python type
Built-in Methods
| Method/Variable | Description |
|---|---|
_input.all() | All items from input |
_input.first() | First item |
_item | Current item (Each Item mode) |
_("Node").all() | Items from named node |
_now | Current Luxon DateTime |
_today | Today at midnight |
_execution | Execution metadata |
_workflow | Workflow metadata |
Debugging with print()
Use print() to output debug information to the browser console:
# Get all items
items = _input.all()
# Print how many items we're processing
# f-strings let you embed variables directly in strings
print(f"Processing {len(items)} items")
# enumerate() gives both index (i) and value (item) in each loop
for i, item in enumerate(items):
# Convert to Python dict so we can see the actual contents
data = item.json.to_py()
# Print each item with its index number
print(f"Item {i}: {data}")
Check the browser’s developer console (F12) to see output.
Python Limitations
Compared to JavaScript, Python mode has some restrictions:
- Slower performance: Pyodide compilation adds overhead
- Limited external packages: Fewer modules available than npm
- Async not supported: Code runs synchronously
- Some built-in methods differ: Check syntax carefully
For straightforward transformations, Python works excellently. For performance-critical or complex workflows, JavaScript may be preferable.
Common Errors and Fixes
These errors appear constantly in the n8n community forum. Here’s how to fix each one.
Error: “Code doesn’t return items properly”
Symptom: Workflow fails immediately after Code node executes.
Cause: Return value doesn’t match the required structure.
Wrong:
// Missing json wrapper
return [{ name: "test" }];
// Not an array
return { json: { name: "test" } };
// json points to array instead of object
return [{ json: ["item1", "item2"] }];
Correct:
return [{
json: {
name: "test"
}
}];
Error: “Cannot read properties of undefined”
Symptom: Error mentions reading property of undefined.
Cause: Accessing a property on data that doesn’t exist.
Wrong:
// user might not exist
const email = item.json.user.email;
Correct:
// Optional chaining prevents error
const email = item.json.user?.email;
// Or with fallback
const email = item.json.user?.email ?? 'unknown';
Error: “SyntaxError: Cannot use import statement”
Symptom: Error when trying to use ES module imports.
Cause: Code node doesn’t support ES module syntax.
Wrong:
import lodash from 'lodash';
Correct:
const lodash = require('lodash');
Note: External modules require self-hosted n8n with proper configuration.
Error: Wrong data in subsequent nodes
Symptom: Next node receives unexpected data or [object Object].
Cause: Returning raw JavaScript objects without proper structure, or not converting JsProxy in Python.
JavaScript fix: Ensure you return [{ json: {...} }] format.
Python fix: Convert JsProxy objects with .to_py() before returning.
Common Error Reference Table
| Error Message | Likely Cause | Fix |
|---|---|---|
| ”Code doesn’t return items properly” | Wrong return structure | Return [{ json: {...} }] |
| ”Cannot read properties of undefined” | Missing data | Use optional chaining ?. |
| ”SyntaxError: Cannot use import” | ES module syntax | Use require() instead |
| ”items is not iterable” | $input.all() returned undefined | Check input connection |
| ”[object Object]” in output | JsProxy not converted | Use .to_py() in Python |
| ”Unexpected token” | Syntax error in code | Check for typos, missing brackets |
Debugging Techniques
When Code nodes misbehave, these techniques help identify the problem quickly.
Console Logging
Add strategic log statements to trace data flow:
JavaScript:
// Start by logging what you received
const items = $input.all();
console.log('Input count:', items.length);
// JSON.stringify with (data, null, 2) formats output nicely
// 'null' means no filtering, '2' means 2-space indentation
console.log('First item:', JSON.stringify(items[0], null, 2));
// ... your transformation logic goes here ...
// Log output before returning to verify it's correct
console.log('Output:', JSON.stringify(result, null, 2));
return result;
Python:
# Log input data at the start
items = _input.all()
print(f"Input count: {len(items)}")
# Use .to_py() to see actual data instead of [object Object]
print(f"First item: {items[0].json.to_py()}")
# ... your transformation logic goes here ...
# Log final result before returning
print(f"Output: {result}")
return result
Check browser developer console (F12) to see output.
Pinned Data Testing
- Run your workflow once to get real data
- Click “Pin Data” on the node before Code
- Now test the Code node repeatedly with consistent input
This isolates the Code node from upstream variability.
Type Checking
When data behaves unexpectedly, check its type:
JavaScript:
// typeof returns: "string", "number", "boolean", "object", "undefined"
console.log('Type:', typeof item.json.value);
// Arrays also return "object" with typeof, so use Array.isArray()
console.log('Is array:', Array.isArray(item.json.items));
// Check if value exists before using it
console.log('Has value:', item.json.value !== undefined);
Python:
# type() shows the Python type of a variable
print(f"Type: {type(item.json)}") # Likely shows JsProxy
# After conversion, shows actual Python type
data = item.json.to_py()
print(f"Type after to_py(): {type(data)}") # Shows <class 'dict'>
Simplify and Rebuild
When stuck:
- Comment out most of your code
- Return simple static data to verify structure works
- Gradually add logic back, testing each addition
This isolates which specific line causes the problem.
For complex debugging scenarios, try our free workflow debugger tool.
Real-World Examples
Example 1: Aggregate Order Data
Scenario: Calculate total revenue and order count from order items.
JavaScript:
// Get all order items from input
const items = $input.all();
// reduce() processes an array and accumulates a single result
// First argument: callback function with (accumulator, currentItem)
// Second argument: initial value for accumulator
const summary = items.reduce((acc, item) => {
// Add this order's amount to running total (use 0 if amount is missing)
acc.totalRevenue += item.json.amount || 0;
// Increment the order counter
acc.orderCount += 1;
// Must return accumulator for next iteration
return acc;
}, { totalRevenue: 0, orderCount: 0 }); // Initial values
// Calculate average after the loop (avoid division by zero)
// toFixed(2) rounds to 2 decimal places and returns a string
summary.averageOrder = summary.orderCount > 0
? (summary.totalRevenue / summary.orderCount).toFixed(2)
: 0;
// Return as a single item containing the summary
return [{
json: summary
}];
Python:
# Get all order items
items = _input.all()
# Initialize counters
total_revenue = 0
order_count = 0
# Loop through each order
for item in items:
# Convert JsProxy to Python dict for safe access
data = item.json.to_py()
# .get() returns the value or a default (0) if key doesn't exist
total_revenue += data.get("amount", 0)
order_count += 1
# Calculate average (ternary expression prevents division by zero)
average = round(total_revenue / order_count, 2) if order_count > 0 else 0
# Return single item with aggregated results
return [{
"json": {
"totalRevenue": total_revenue,
"orderCount": order_count,
"averageOrder": average
}
}]
Example 2: Transform API Response
Scenario: Flatten nested user data from an API into a simple structure.
JavaScript:
// Get the API response (usually a single item with nested data)
const items = $input.all();
const apiResponse = items[0].json;
// Safely access nested array: data.users (use empty array as fallback)
// Optional chaining (?.) prevents errors if 'data' doesn't exist
const users = (apiResponse.data?.users || []).map(user => ({
// Each user becomes a separate n8n item
json: {
id: user.id,
// Template literal combines first and last name; trim() removes extra spaces
fullName: `${user.firstName} ${user.lastName}`.trim(),
// Chain optional access for deeply nested properties
email: user.contact?.email?.toLowerCase(),
// Use null as explicit "no value" indicator
phone: user.contact?.phone || null,
// Parse ISO date and reformat to simple date string
createdAt: DateTime.fromISO(user.createdAt).toFormat('yyyy-MM-dd')
}
}));
// Return array of user items (one item per user)
return users;
Python:
# Get the API response
items = _input.all()
# Must convert to Python dict to use .get() method
api_response = items[0].json.to_py()
# Safely navigate nested structure with .get() and default values
# .get("key", default) returns default if key doesn't exist
users = api_response.get("data", {}).get("users", [])
result = []
for user in users:
result.append({
"json": {
"id": user.get("id"),
# f-string combines names; .strip() removes leading/trailing whitespace
"fullName": f"{user.get('firstName', '')} {user.get('lastName', '')}".strip(),
# Conditional expression handles missing 'contact' object
"email": user.get("contact", {}).get("email", "").lower() if user.get("contact") else None,
"phone": user.get("contact", {}).get("phone"),
# Slice string to get first 10 characters (YYYY-MM-DD)
"createdAt": user.get("createdAt", "")[:10]
}
})
return result
Example 3: Group and Count Items
Scenario: Group products by category and count items per category.
JavaScript:
// Get all product items
const items = $input.all();
// Use reduce() to build a grouped object
// Start with empty object {} as initial accumulator
const grouped = items.reduce((acc, item) => {
// Get category (or default to 'Uncategorized')
const category = item.json.category || 'Uncategorized';
// If this category doesn't exist yet, create it
if (!acc[category]) {
acc[category] = { count: 0, items: [] };
}
// Increment count and add product name to list
acc[category].count += 1;
acc[category].items.push(item.json.name);
return acc;
}, {}); // Initial value: empty object
// Return summary with all categories and their data
return [{
json: {
// Object.keys() returns array of all category names
categories: Object.keys(grouped),
// The full grouped data structure
breakdown: grouped,
// Total number of unique categories
totalCategories: Object.keys(grouped).length
}
}];
Python:
# Get all product items
items = _input.all()
# Initialize empty dictionary for grouping
grouped = {}
for item in items:
# Convert JsProxy to Python dict
data = item.json.to_py()
# Get category with fallback to "Uncategorized"
category = data.get("category", "Uncategorized")
# Create category entry if it doesn't exist
if category not in grouped:
grouped[category] = {"count": 0, "items": []}
# Increment count and add product name
grouped[category]["count"] += 1
grouped[category]["items"].append(data.get("name"))
# Return summary with category breakdown
return [{
"json": {
# list() converts dict_keys to a regular list
"categories": list(grouped.keys()),
# The full grouped data structure
"breakdown": grouped,
# len() counts unique categories
"totalCategories": len(grouped)
}
}]
For 25 additional JavaScript recipes covering dates, strings, arrays, and API responses, see our complete JavaScript Code node recipe guide.
Pro Tips and Best Practices
1. Start with Expressions
Before reaching for the Code node, check if n8n expressions can handle your transformation. Expressions work in any node field and handle many common cases without code.
2. Name Your Code Nodes
Instead of “Code” or “Code1”, use descriptive names:
- “Calculate Order Totals”
- “Transform API Response”
- “Group By Category”
This makes workflows self-documenting.
3. Keep Code Focused
Each Code node should do one thing well. If your code exceeds 50 lines, consider:
- Breaking into multiple Code nodes
- Using a sub-workflow
- Whether a different approach is cleaner
4. Handle Empty Inputs
Check for empty input before processing:
// Always get input items first
const items = $input.all();
// Check if array is empty before processing
// This prevents errors when looping over empty arrays
if (items.length === 0) {
// Return a message item instead of crashing
return [{ json: { message: 'No items to process' } }];
}
// Safe to continue - we know items exist
// ... your processing logic here ...
5. Validate Before Transform
When data comes from external sources, validate it:
// Get all input items
const items = $input.all();
return items.map(item => {
// Extract the value to validate
const email = item.json.email;
// Simple validation: email exists AND contains '@'
// Returns true or false
const isValidEmail = email && email.includes('@');
return {
json: {
// Spread operator (...) copies all original fields
...item.json,
// Add validation result as new field
emailValid: isValidEmail,
// Add timestamp for when this was processed
processedAt: $now.toISO()
}
};
});
6. Test with Edge Cases
Before deploying, test your Code node with:
- Empty arrays
- Null values
- Missing fields
- Unexpected data types
- Very large datasets
Our expression validator tool helps test expressions, and the JSON fixer tool helps with malformed JSON.
For production workflows requiring robust error handling and complex transformations, our workflow development services can help design maintainable solutions. For architectural guidance, explore our n8n consulting services.
Frequently Asked Questions
Can I make HTTP requests from the Code node?
No, the Code node runs synchronously and cannot make external HTTP requests directly. Use the HTTP Request node for API calls, then process the response in a subsequent Code node if needed. This separation keeps your workflow visible and debuggable. The Code node is designed for data transformation, not external communication.
Why is my Python Code node so slow compared to JavaScript?
Python in n8n runs via Pyodide, which compiles Python to WebAssembly. This compilation step adds significant overhead, especially on first execution. JavaScript runs natively and is substantially faster. For workflows processing thousands of items or running frequently, JavaScript is the better choice. Python mode is ideal when familiarity matters more than raw performance, or for simpler transformations where the speed difference is negligible.
How do I return multiple items from a single input item?
Return an array with multiple objects, each wrapped in the required structure. For example, to split one order into its line items:
// Get all order items
const items = $input.all();
// This array will hold all the line items we extract
const allLineItems = [];
// Loop through each order
for (const item of items) {
// Loop through line items within this order (use empty array if none exist)
for (const lineItem of item.json.lineItems || []) {
// Create a new n8n item for each line item
allLineItems.push({
json: {
// Include the parent order ID for reference
orderId: item.json.orderId,
// Spread all line item properties into this object
...lineItem
}
});
}
}
// Return all extracted line items
// If one order has 5 line items, this returns 5 separate n8n items
return allLineItems;
Each object in the returned array becomes a separate item in the workflow.
What’s the difference between $json and $input.first().json?
In “Run Once for Each Item” mode, $json refers to the current item being processed. In “Run Once for All Items” mode, $json is not available because no single “current item” exists. Use $input.first().json when you specifically need the first item’s data in All Items mode, or $input.all() to get every item. The key is understanding which mode your Code node uses and accessing data accordingly.
How can I access environment variables in the Code node?
Use $env to access environment variables configured in your n8n instance:
// Access environment variables using $env.VARIABLE_NAME
// These are set in your n8n instance configuration, not in the workflow
const apiEndpoint = $env.API_ENDPOINT;
// Environment variables are always strings
// Compare to string 'true' to get boolean behavior
const debugMode = $env.DEBUG_MODE === 'true';
// Use in your logic
if (debugMode) {
console.log('Debug mode enabled, calling:', apiEndpoint);
}
For self-hosted n8n, set these in your environment or .env file. For n8n Cloud, configure them in your instance settings. Never hardcode sensitive values like API keys in the Code node itself. Use n8n’s credentials system or environment variables to keep secrets secure. See our workflow best practices guide for more on credential management.