data/skills/n8n-code-javascript/BUILTIN_FUNCTIONS.md
Complete reference for n8n's built-in JavaScript functions and helpers.
n8n Code nodes provide powerful built-in functions beyond standard JavaScript. This guide covers:
$helpers global is undefined in the task runner)Since n8n v2.0, Code nodes execute inside a task runner sandbox (JsTaskRunnerSandbox) which deliberately blocks several APIs. The legacy vm2 sandbox is being removed. Knowing what's blocked saves hours of "why does this throw on activation but not in the editor preview."
// ❌ BLOCKED — throws UnsupportedFunctionError
await this.helpers.httpRequestWithAuthentication.call(this, 'credType', { ... });
await this.helpers.requestWithAuthenticationPaginated.call(this, { ... }, 'credType');
n8n's source comment explains why: "these rely on checking the credentials from the current node type (Code Node), and Code Node doesn't have credentials." There is no env var to re-enable them in the task runner — the deny-list is compiled-in (packages/@n8n/task-runner/src/runner-types.ts).
Workaround: don't try to authenticate from inside a Code node. Instead, either:
$env may be blocked$env is gated by N8N_BLOCK_ENV_ACCESS_IN_NODE. When set to true (a common production hardening), any reference to $env.SOMETHING throws. Since you can't tell from inside the Code node whether it's enabled, don't rely on $env for portable skills — treat secrets as a credential concern (HTTP Request node) rather than a Code-node concern.
require() is gated by allowlists// May throw "Cannot find module 'crypto'" — depends on env vars
const crypto = require('crypto');
Built-in modules need N8N_RUNNERS_ALLOWED_BUILT_IN_MODULES (or legacy NODE_FUNCTION_ALLOW_BUILTIN) set to * or a comma-list including crypto. External npm packages need N8N_RUNNERS_ALLOWED_EXTERNAL_MODULES plus the package being installed in the runner image. On default installs neither is set — require() throws.
Buffer and URL are globals (not require'd), so they always work.
$input.*, $json, $node[…], this.helpers.httpRequest() (without auth), $jmespath(), $getWorkflowStaticData(), DateTime (Luxon), and all standard JavaScript globals (Math, JSON, Object, Array, console, Buffer, URL, URLSearchParams).
Accessor gotcha: the bare
$helpersglobal is undefined in the task-runner sandbox —$helpers.httpRequest()throwsReferenceError: $helpers is not defined. The working accessor isthis.helpers.httpRequest()(inside a nested async function wherethisis lost, call it asawait fn.call(this, ...)). The n8n-mcp validator may wrongly suggest$helpers— ignore it. And for anything beyond a trivial unauthenticated GET (pagination, retries, credentials), prefer the HTTP Request node and keep Code nodes for pure logic.
Make HTTP requests directly from Code nodes without using HTTP Request node. The accessor is this.helpers.httpRequest() — the bare $helpers global is undefined in the task-runner sandbox and throws ReferenceError: $helpers is not defined. For non-trivial calls (pagination, retries, credentials) prefer the HTTP Request node.
const response = await this.helpers.httpRequest({
method: 'GET',
url: 'https://api.example.com/users'
});
return [{json: {data: response}}];
const response = await this.helpers.httpRequest({
method: 'POST', // GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS
url: 'https://api.example.com/users',
headers: {
'Authorization': 'Bearer token123',
'Content-Type': 'application/json',
'User-Agent': 'n8n-workflow'
},
body: {
name: 'John Doe',
email: '[email protected]'
},
qs: { // Query string parameters
page: 1,
limit: 10
},
timeout: 10000, // Milliseconds (default: no timeout)
json: true, // Auto-parse JSON response (default: true)
simple: false, // Don't throw on HTTP errors (default: true)
resolveWithFullResponse: false // Return only body (default: false)
});
// Simple GET
const users = await this.helpers.httpRequest({
method: 'GET',
url: 'https://api.example.com/users'
});
return [{json: {users}}];
// GET with query parameters
const results = await this.helpers.httpRequest({
method: 'GET',
url: 'https://api.example.com/search',
qs: {
q: 'javascript',
page: 1,
per_page: 50
}
});
return [{json: results}];
// POST with JSON body
// NOTE: For authenticated APIs, prefer an HTTP Request node with a credential
// attached. Embedding the token in a Code node only works when (a) the token
// arrives as runtime data (e.g. from a previous node), or (b) you're sure
// $env access is enabled on this instance. See section 0.
const apiToken = $input.first().json.apiToken; // passed in from a credential-aware upstream node
const newUser = await this.helpers.httpRequest({
method: 'POST',
url: 'https://api.example.com/users',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${apiToken}`
},
body: {
name: $json.body.name,
email: $json.body.email,
role: 'user'
}
});
return [{json: newUser}];
// Update resource
const updated = await this.helpers.httpRequest({
method: 'PATCH',
url: `https://api.example.com/users/${userId}`,
body: {
name: 'Updated Name',
status: 'active'
}
});
return [{json: updated}];
// Delete resource
await this.helpers.httpRequest({
method: 'DELETE',
url: `https://api.example.com/users/${userId}`,
headers: {
'Authorization': `Bearer ${apiToken}` // token passed in from upstream node, not $env
}
});
return [{json: {deleted: true, userId}}];
Strong preference: don't authenticate from inside a Code node. Use an HTTP Request node with a credential attached, or delegate to a sub-workflow whose HTTP Request node holds the credential. The patterns below only apply when the token genuinely flows through the workflow as data.
// Bearer Token (token came from a previous node, not $env)
const response = await this.helpers.httpRequest({
url: 'https://api.example.com/data',
headers: {
'Authorization': `Bearer ${$input.first().json.token}`
}
});
// API Key in Header (key came from a previous node, not $env)
const response = await this.helpers.httpRequest({
url: 'https://api.example.com/data',
headers: {
'X-API-Key': $input.first().json.apiKey
}
});
// Basic Auth (manual)
const credentials = Buffer.from(`${username}:${password}`).toString('base64');
const response = await this.helpers.httpRequest({
url: 'https://api.example.com/data',
headers: {
'Authorization': `Basic ${credentials}`
}
});
// Handle HTTP errors gracefully
try {
const response = await this.helpers.httpRequest({
method: 'GET',
url: 'https://api.example.com/users',
simple: false // Don't throw on 4xx/5xx
});
if (response.statusCode >= 200 && response.statusCode < 300) {
return [{json: {success: true, data: response.body}}];
} else {
return [{
json: {
success: false,
status: response.statusCode,
error: response.body
}
}];
}
} catch (error) {
return [{
json: {
success: false,
error: error.message
}
}];
}
// Get full response including headers and status
const response = await this.helpers.httpRequest({
url: 'https://api.example.com/data',
resolveWithFullResponse: true
});
return [{
json: {
statusCode: response.statusCode,
headers: response.headers,
body: response.body,
rateLimit: response.headers['x-ratelimit-remaining']
}
}];
n8n includes Luxon for powerful date/time handling. Access via DateTime global.
// Current time
const now = DateTime.now();
// Current time in specific timezone
const nowTokyo = DateTime.now().setZone('Asia/Tokyo');
// Today at midnight
const today = DateTime.now().startOf('day');
return [{
json: {
iso: now.toISO(), // "2025-01-20T15:30:00.000Z"
formatted: now.toFormat('yyyy-MM-dd HH:mm:ss'), // "2025-01-20 15:30:00"
unix: now.toSeconds(), // Unix timestamp
millis: now.toMillis() // Milliseconds since epoch
}
}];
const now = DateTime.now();
return [{
json: {
isoFormat: now.toISO(), // ISO 8601: "2025-01-20T15:30:00.000Z"
sqlFormat: now.toSQL(), // SQL: "2025-01-20 15:30:00.000"
httpFormat: now.toHTTP(), // HTTP: "Mon, 20 Jan 2025 15:30:00 GMT"
// Custom formats
dateOnly: now.toFormat('yyyy-MM-dd'), // "2025-01-20"
timeOnly: now.toFormat('HH:mm:ss'), // "15:30:00"
readable: now.toFormat('MMMM dd, yyyy'), // "January 20, 2025"
compact: now.toFormat('yyyyMMdd'), // "20250120"
withDay: now.toFormat('EEEE, MMMM dd, yyyy'), // "Monday, January 20, 2025"
custom: now.toFormat('dd/MM/yy HH:mm') // "20/01/25 15:30"
}
}];
// From ISO string
const dt1 = DateTime.fromISO('2025-01-20T15:30:00');
// From specific format
const dt2 = DateTime.fromFormat('01/20/2025', 'MM/dd/yyyy');
// From SQL
const dt3 = DateTime.fromSQL('2025-01-20 15:30:00');
// From Unix timestamp
const dt4 = DateTime.fromSeconds(1737384600);
// From milliseconds
const dt5 = DateTime.fromMillis(1737384600000);
return [{json: {parsed: dt1.toISO()}}];
const now = DateTime.now();
return [{
json: {
// Adding time
tomorrow: now.plus({days: 1}).toISO(),
nextWeek: now.plus({weeks: 1}).toISO(),
nextMonth: now.plus({months: 1}).toISO(),
inTwoHours: now.plus({hours: 2}).toISO(),
// Subtracting time
yesterday: now.minus({days: 1}).toISO(),
lastWeek: now.minus({weeks: 1}).toISO(),
lastMonth: now.minus({months: 1}).toISO(),
twoHoursAgo: now.minus({hours: 2}).toISO(),
// Complex operations
in90Days: now.plus({days: 90}).toFormat('yyyy-MM-dd'),
in6Months: now.plus({months: 6}).toFormat('yyyy-MM-dd')
}
}];
const now = DateTime.now();
const targetDate = DateTime.fromISO('2025-12-31');
return [{
json: {
// Comparisons
isFuture: targetDate > now,
isPast: targetDate < now,
isEqual: targetDate.equals(now),
// Differences
daysUntil: targetDate.diff(now, 'days').days,
hoursUntil: targetDate.diff(now, 'hours').hours,
monthsUntil: targetDate.diff(now, 'months').months,
// Detailed difference
detailedDiff: targetDate.diff(now, ['months', 'days', 'hours']).toObject()
}
}];
const now = DateTime.now();
return [{
json: {
// Current timezone
local: now.toISO(),
// Convert to different timezone
tokyo: now.setZone('Asia/Tokyo').toISO(),
newYork: now.setZone('America/New_York').toISO(),
london: now.setZone('Europe/London').toISO(),
utc: now.toUTC().toISO(),
// Get timezone info
timezone: now.zoneName, // "America/Los_Angeles"
offset: now.offset, // Offset in minutes
offsetFormatted: now.toFormat('ZZ') // "+08:00"
}
}];
const now = DateTime.now();
return [{
json: {
startOfDay: now.startOf('day').toISO(),
endOfDay: now.endOf('day').toISO(),
startOfWeek: now.startOf('week').toISO(),
endOfWeek: now.endOf('week').toISO(),
startOfMonth: now.startOf('month').toISO(),
endOfMonth: now.endOf('month').toISO(),
startOfYear: now.startOf('year').toISO(),
endOfYear: now.endOf('year').toISO()
}
}];
const now = DateTime.now();
return [{
json: {
// Day info
weekday: now.weekday, // 1 = Monday, 7 = Sunday
weekdayShort: now.weekdayShort, // "Mon"
weekdayLong: now.weekdayLong, // "Monday"
isWeekend: now.weekday > 5, // Saturday or Sunday
// Month info
month: now.month, // 1-12
monthShort: now.monthShort, // "Jan"
monthLong: now.monthLong, // "January"
// Year info
year: now.year, // 2025
quarter: now.quarter, // 1-4
daysInMonth: now.daysInMonth // 28-31
}
}];
Query and transform JSON structures using JMESPath syntax.
const data = $input.first().json;
// Extract specific field
const names = $jmespath(data, 'users[*].name');
// Filter array
const adults = $jmespath(data, 'users[?age >= `18`]');
// Get specific index
const firstUser = $jmespath(data, 'users[0]');
return [{json: {names, adults, firstUser}}];
const data = $input.first().json;
// Sort and slice
const top5 = $jmespath(data, 'users | sort_by(@, &score) | reverse(@) | [0:5]');
// Extract nested fields
const emails = $jmespath(data, 'users[*].contact.email');
// Multi-field extraction
const simplified = $jmespath(data, 'users[*].{name: name, email: contact.email}');
// Conditional filtering
const premium = $jmespath(data, 'users[?subscription.tier == `premium`]');
return [{json: {top5, emails, simplified, premium}}];
// Pattern 1: Filter and project
const query1 = $jmespath(data, 'products[?price > `100`].{name: name, price: price}');
// Pattern 2: Aggregate functions
const query2 = $jmespath(data, 'sum(products[*].price)');
const query3 = $jmespath(data, 'max(products[*].price)');
const query4 = $jmespath(data, 'length(products)');
// Pattern 3: Nested filtering
const query5 = $jmespath(data, 'categories[*].products[?inStock == `true`]');
return [{json: {query1, query2, query3, query4, query5}}];
Store data that persists across workflow executions.
// Get static data storage
const staticData = $getWorkflowStaticData();
// Initialize counter if doesn't exist
if (!staticData.counter) {
staticData.counter = 0;
}
// Increment counter
staticData.counter++;
return [{
json: {
executionCount: staticData.counter
}
}];
// Use Case 1: Rate limiting
const staticData = $getWorkflowStaticData();
const now = Date.now();
if (!staticData.lastRun) {
staticData.lastRun = now;
staticData.runCount = 1;
} else {
const timeSinceLastRun = now - staticData.lastRun;
if (timeSinceLastRun < 60000) { // Less than 1 minute
return [{json: {error: 'Rate limit: wait 1 minute between runs'}}];
}
staticData.lastRun = now;
staticData.runCount++;
}
return [{json: {allowed: true, totalRuns: staticData.runCount}}];
// Use Case 2: Tracking last processed ID
const staticData = $getWorkflowStaticData();
const currentItems = $input.all();
// Get last processed ID
const lastId = staticData.lastProcessedId || 0;
// Filter only new items
const newItems = currentItems.filter(item => item.json.id > lastId);
// Update last processed ID
if (newItems.length > 0) {
staticData.lastProcessedId = Math.max(...newItems.map(item => item.json.id));
}
return newItems;
// Use Case 3: Accumulating results
const staticData = $getWorkflowStaticData();
if (!staticData.accumulated) {
staticData.accumulated = [];
}
// Add current items to accumulated list
const currentData = $input.all().map(item => item.json);
staticData.accumulated.push(...currentData);
return [{
json: {
currentBatch: currentData.length,
totalAccumulated: staticData.accumulated.length,
allData: staticData.accumulated
}
}];
return [{
json: {
// Rounding
rounded: Math.round(3.7), // 4
floor: Math.floor(3.7), // 3
ceil: Math.ceil(3.2), // 4
// Min/Max
max: Math.max(1, 5, 3, 9, 2), // 9
min: Math.min(1, 5, 3, 9, 2), // 1
// Random
random: Math.random(), // 0-1
randomInt: Math.floor(Math.random() * 100), // 0-99
// Other
abs: Math.abs(-5), // 5
sqrt: Math.sqrt(16), // 4
pow: Math.pow(2, 3) // 8
}
}];
// Parse JSON string
const jsonString = '{"name": "John", "age": 30}';
const parsed = JSON.parse(jsonString);
// Stringify object
const obj = {name: "John", age: 30};
const stringified = JSON.stringify(obj);
// Pretty print
const pretty = JSON.stringify(obj, null, 2);
return [{json: {parsed, stringified, pretty}}];
// Debug logging (appears in browser console, press F12)
console.log('Processing items:', $input.all().length);
console.log('First item:', $input.first().json);
// Other console methods
console.error('Error message');
console.warn('Warning message');
console.info('Info message');
// Continues to return data
return [{json: {processed: true}}];
const obj = {name: "John", age: 30, city: "NYC"};
return [{
json: {
keys: Object.keys(obj), // ["name", "age", "city"]
values: Object.values(obj), // ["John", 30, "NYC"]
entries: Object.entries(obj), // [["name", "John"], ...]
// Check property
hasName: 'name' in obj, // true
// Merge objects
merged: Object.assign({}, obj, {country: "USA"})
}
}];
const arr = [1, 2, 3, 4, 5];
return [{
json: {
mapped: arr.map(x => x * 2), // [2, 4, 6, 8, 10]
filtered: arr.filter(x => x > 2), // [3, 4, 5]
reduced: arr.reduce((sum, x) => sum + x, 0), // 15
some: arr.some(x => x > 3), // true
every: arr.every(x => x > 0), // true
find: arr.find(x => x > 3), // 4
includes: arr.includes(3), // true
joined: arr.join(', ') // "1, 2, 3, 4, 5"
}
}];
Gated:
require('crypto')only works ifN8N_RUNNERS_ALLOWED_BUILT_IN_MODULES(or legacyNODE_FUNCTION_ALLOW_BUILTIN) includescrypto(or is*). On default installs it throws "Cannot find module 'crypto'". For hashing you control, prefer doing it before reaching the Code node, or — if you must — verify your instance's config first.
const crypto = require('crypto');
// Hash functions
const hash = crypto.createHash('sha256')
.update('my secret text')
.digest('hex');
// MD5 hash
const md5 = crypto.createHash('md5')
.update('my text')
.digest('hex');
// Random values
const randomBytes = crypto.randomBytes(16).toString('hex');
return [{json: {hash, md5, randomBytes}}];
// Base64 encoding
const encoded = Buffer.from('Hello World').toString('base64');
// Base64 decoding
const decoded = Buffer.from(encoded, 'base64').toString();
// Hex encoding
const hex = Buffer.from('Hello').toString('hex');
return [{json: {encoded, decoded, hex}}];
// Parse URL
const url = new URL('https://example.com/path?param1=value1¶m2=value2');
// Build query string
const params = new URLSearchParams({
search: 'query',
page: 1,
limit: 10
});
return [{
json: {
host: url.host,
pathname: url.pathname,
search: url.search,
queryString: params.toString() // "search=query&page=1&limit=10"
}
}];
External npm packages are NOT available (unless explicitly allowlisted via N8N_RUNNERS_ALLOWED_EXTERNAL_MODULES and installed in the runner image — rare):
Authentication helpers are blocked in the task runner sandbox (see section 0):
this.helpers.httpRequestWithAuthenticationthis.helpers.requestWithAuthenticationPaginatedConditionally blocked (depends on instance config):
$env.* — blocked when N8N_BLOCK_ENV_ACCESS_IN_NODE=truerequire('crypto') / require('fs') / etc. — blocked unless N8N_RUNNERS_ALLOWED_BUILT_IN_MODULES includes themWorkarounds:
Most Useful Built-ins:
$helpers global is undefined)Common Patterns:
See Also: