Run Custom Code

Execute JavaScript code directly in your HubSpot workflows to transform data, call APIs, and implement custom logic.

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

1

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.

2

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.

3

Map input fields

Map up to 3 HubSpot properties to input1, input2, and input3. These become available in your code via the inputs object.

4

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.

When you select a snippet, the latest version of the snippet is used every time the workflow runs. Update the snippet in your dashboard and all workflows using it automatically get the new version.

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.

Example: Inline code
// 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:

InputAccess in CodeDescription
input1inputs.input1First HubSpot property (e.g., email)
input2inputs.input2Second HubSpot property (e.g., company)
input3inputs.input3Third 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}` }
});
Never hard-code API keys in your code. Always use secrets. Secrets are encrypted with AES-256-GCM and only decrypted during execution. They are never logged or exposed.

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 ID

output 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" });
Output values must be strings. Use 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:

JSONDateMathNumberStringArrayObjectMapSetRegExpPromiseparseIntparseFloatisNaNisFiniteencodeURIComponentdecodeURIComponentfetch
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.

Lead Score Calculator
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.

Name Formatter
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 personalization

3Call an external API

Use fetch with a secret API key to enrich contact data. Map input1 to email.

Company Enrichment via API
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.

Lead Routing Logic
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. 1.Go to Secrets in your dashboard.
  2. 2.Click "Add Secret" and enter a name (e.g., STRIPE_KEY) and the value.
  3. 3.Save. The value is encrypted immediately. You can never view the value again, only update or delete it.
  4. 4.In your code, access it via secrets.STRIPE_KEY.
Secret names must be uppercase with underscores (e.g., MY_API_KEY). The free plan allows up to 3 secrets, Starter allows 10, and Pro/Agency plans have unlimited secrets.

Limits

LimitStarterProCustom
Max code length50 KB50 KB50 KB
Execution timeout20s30s60s
Console output lines100100100
Snippets25UnlimitedUnlimited
Secrets1025Unlimited

Security

Code runs in an isolated sandbox with strict security restrictions:

X

No file system access

fs, path, and all Node.js file APIs are blocked.

X

No process access

process, child_process, exec, and spawn are blocked.

X

No module imports

require() and import are blocked. Only built-in globals are available.

X

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.

Continue