Run Custom Code
Execute JavaScript code directly in your HubSpot workflows to transform data, call APIs, and implement custom logic.
On this page
Overview
The Run Custom Code action executes JavaScript code within your HubSpot workflow. It runs in a secure sandbox environment with access to HubSpot property values, your encrypted secrets, and workflow context.
You can use it to perform calculations, transform data, call external APIs, implement conditional logic, format values, and anything else that JavaScript can do -- all without leaving HubSpot.
When to use Run Custom Code
- Calculate a lead score based on multiple properties
- Call an external enrichment API and store results back in HubSpot
- Apply complex conditional logic that HubSpot branches cannot handle
- Transform and reformat data between different systems
- Generate unique identifiers, tokens, or formatted strings
Setup
Add the action to your workflow
In your HubSpot workflow editor, click + to add a new action. Search for "AuPerate: Run Code" under Custom Actions.
Choose your code source
Either select a saved snippet from your AuPerate dashboard or write inline code directly in the action configuration. Snippets are recommended for reusable logic.
Map input fields
Map up to 3 HubSpot properties to input1, input2, and input3. These become available in your code via the inputs object.
Save and publish
Your code runs every time a record enters that workflow step. Output values are available in subsequent workflow actions.
Configuration
Snippet ID
Select a saved code snippet from your AuPerate dashboard. Snippets are reusable pieces of JavaScript that you can manage, version, and test from the dashboard before deploying to your workflows.
Inline Code
Write JavaScript directly in the action configuration field. This is useful for quick, one-off logic that does not need to be reused across workflows.
// Access HubSpot properties via inputs
const email = inputs.input1;
const company = inputs.input2;
// Calculate and return a result
const domain = email ? email.split('@')[1] : '';
output.output1 = domain;
output.output2 = company ? company.toUpperCase() : 'UNKNOWN';Input Fields
Map HubSpot properties to three input slots. These become available in your code:
| Input | Access in Code | Description |
|---|---|---|
| input1 | inputs.input1 | First HubSpot property (e.g., email) |
| input2 | inputs.input2 | Second HubSpot property (e.g., company) |
| input3 | inputs.input3 | Third HubSpot property (e.g., deal_amount) |
Available APIs in Sandbox
Your code runs in a secure sandbox. The following objects and APIs are available:
inputs object
Contains the HubSpot property values mapped to input1, input2, and input3.
// Access input values
const email = inputs.input1; // e.g., "john@example.com"
const amount = inputs.input2; // e.g., "5000"
const stage = inputs.input3; // e.g., "closedwon"
// Input values are always strings
const numericAmount = parseFloat(amount) || 0;secrets object
Access your encrypted secrets stored in the AuPerate dashboard. Secrets are automatically decrypted at runtime.
// Access secrets by name
const apiKey = secrets.CLEARBIT_API_KEY;
const stripeKey = secrets.STRIPE_SECRET_KEY;
const webhookSecret = secrets.MY_WEBHOOK_SECRET;
// Use in API calls
const response = await fetch('https://api.example.com/data', {
headers: { 'Authorization': `Bearer ${apiKey}` }
});context object
Provides metadata about the current execution context.
// Execution context
context.portalId // HubSpot portal ID (number)
context.object // Object type: "contact", "company", "deal", "ticket"
context.workflow // Workflow name or IDoutput object
Set output values that become available in subsequent workflow steps. Up to 3 outputs.
// Set output values (must be strings)
output.output1 = "calculated_value";
output.output2 = String(42);
output.output3 = JSON.stringify({ key: "value" });String() or JSON.stringify() to convert numbers and objects.console.log()
Log messages for debugging. All console output is captured and visible in the execution logs on your AuPerate dashboard.
console.log("Processing contact:", inputs.input1);
console.log("Calculated score:", score);
console.log("API response:", JSON.stringify(data));Built-in JavaScript APIs
The following standard JavaScript APIs are available in the sandbox:
fetch is available for making HTTP requests to external APIs. The same SSRF protections as Send Webhook apply -- only public HTTPS URLs are allowed.Examples
1Calculate a custom lead score
Map input1 to lifecyclestage, input2 to num_associated_deals, input3 to hs_email_open_count.
const stage = inputs.input1 || '';
const deals = parseInt(inputs.input2) || 0;
const emailOpens = parseInt(inputs.input3) || 0;
let score = 0;
// Stage-based scoring
const stageScores = {
'subscriber': 10,
'lead': 20,
'marketingqualifiedlead': 40,
'salesqualifiedlead': 60,
'opportunity': 80,
'customer': 100
};
score += stageScores[stage.toLowerCase()] || 5;
// Engagement scoring
score += Math.min(deals * 15, 30); // Up to 30 points for deals
score += Math.min(emailOpens * 2, 20); // Up to 20 points for opens
// Cap at 100
score = Math.min(score, 100);
output.output1 = String(score);
output.output2 = score >= 70 ? 'hot' : score >= 40 ? 'warm' : 'cold';
console.log(`Lead score: ${score} (${output.output2})`);2Format a full name from first and last name
Map input1 to firstname and input2 to lastname.
const first = (inputs.input1 || '').trim();
const last = (inputs.input2 || '').trim();
// Capitalize first letter of each word
function titleCase(str) {
return str.replace(/\b\w/g, c => c.toUpperCase());
}
const fullName = titleCase([first, last].filter(Boolean).join(' '));
const initials = [first, last]
.filter(Boolean)
.map(n => n[0].toUpperCase())
.join('');
output.output1 = fullName; // "John Smith"
output.output2 = initials; // "JS"
output.output3 = first || 'there'; // Fallback for personalization3Call an external API
Use fetch with a secret API key to enrich contact data. Map input1 to email.
const email = inputs.input1;
if (!email) {
output.output1 = '';
output.output2 = 'No email provided';
} else {
try {
const domain = email.split('@')[1];
const res = await fetch(
`https://company.clearbit.com/v2/companies/find?domain=${domain}`,
{
headers: {
'Authorization': `Bearer ${secrets.CLEARBIT_KEY}`
}
}
);
if (res.ok) {
const data = await res.json();
output.output1 = data.name || '';
output.output2 = String(data.metrics?.employees || '');
output.output3 = data.category?.industry || '';
console.log(`Enriched: ${data.name}, ${data.metrics?.employees} employees`);
} else {
output.output1 = '';
output.output2 = `API error: ${res.status}`;
console.log('Clearbit error:', res.status);
}
} catch (err) {
output.output2 = `Error: ${err.message}`;
console.log('Fetch error:', err.message);
}
}4Conditional logic based on properties
Route leads to different teams based on deal amount and region.
const amount = parseFloat(inputs.input1) || 0;
const country = (inputs.input2 || '').toLowerCase();
// Define regions
const apac = ['india', 'australia', 'japan', 'singapore', 'china'];
const emea = ['uk', 'germany', 'france', 'uae', 'south africa'];
let team, priority;
if (amount >= 50000) {
team = 'enterprise';
priority = 'high';
} else if (amount >= 10000) {
team = 'mid-market';
priority = 'medium';
} else {
team = 'smb';
priority = 'low';
}
// Add region suffix
if (apac.includes(country)) {
team += '-apac';
} else if (emea.includes(country)) {
team += '-emea';
} else {
team += '-americas';
}
output.output1 = team; // e.g., "enterprise-apac"
output.output2 = priority; // e.g., "high"
output.output3 = String(amount);
console.log(`Routed to ${team} with ${priority} priority`);Snippets
Snippets are reusable JavaScript code blocks managed from the AuPerate dashboard. They offer several advantages over inline code:
Reusability
Use the same snippet across multiple workflows. Change once, update everywhere.
Versioning
Every save creates a new version. Roll back instantly if something breaks.
Testing
Test snippets with sample inputs directly in the dashboard before deploying.
Organization
Name, describe, and categorize your code. Find snippets quickly when you need them.
Manage snippets at /snippets in your dashboard. The free plan allows up to 5 snippets, Starter allows 25, and Pro/Agency plans have unlimited snippets.
Secrets
Secrets are encrypted key-value pairs for storing API keys, tokens, passwords, and other sensitive data. They are encrypted with AES-256-GCM and only decrypted inside the sandbox during execution.
How to create a secret
- 1.Go to Secrets in your dashboard.
- 2.Click "Add Secret" and enter a name (e.g.,
STRIPE_KEY) and the value. - 3.Save. The value is encrypted immediately. You can never view the value again, only update or delete it.
- 4.In your code, access it via
secrets.STRIPE_KEY.
MY_API_KEY). The free plan allows up to 3 secrets, Starter allows 10, and Pro/Agency plans have unlimited secrets.Limits
| Limit | Starter | Pro | Custom |
|---|---|---|---|
| Max code length | 50 KB | 50 KB | 50 KB |
| Execution timeout | 20s | 30s | 60s |
| Console output lines | 100 | 100 | 100 |
| Snippets | 25 | Unlimited | Unlimited |
| Secrets | 10 | 25 | Unlimited |
Security
Code runs in an isolated sandbox with strict security restrictions:
No file system access
fs, path, and all Node.js file APIs are blocked.
No process access
process, child_process, exec, and spawn are blocked.
No module imports
require() and import are blocked. Only built-in globals are available.
No eval or Function constructor
Dynamic code execution via eval() and new Function() is blocked.
SSRF protection on fetch
Only public HTTPS URLs are allowed. Private IPs, localhost, and metadata endpoints are blocked.
Memory and CPU limits
Each execution is limited in memory and CPU time. Infinite loops are automatically terminated.
Next: Format Data
Transform data without writing code using 50+ built-in operations.