Skip to content
SolutionArg

Automation

Custom-Coded Workflow Actions in HubSpot: A Practical Guide

What HubSpot custom code actions do, which plan you need, their 20-second and 128 MB limits, and a verified Node.js pattern with secrets and retries.

By SolutionArg6 min read

TL;DR

  • A custom code action is a small piece of JavaScript (Node.js) you drop into a HubSpot workflow. Python is in beta. It calls APIs, reshapes data or applies logic the standard actions can't.
  • It requires Data Hub Professional or Enterprise (HubSpot Developers).
  • Hard limits: finish within 20 seconds and use at most 128 MB of memory. You can pass up to 50 properties in, and secrets share a 1,000-character budget.
  • If a call fails with a rate-limit, 429 or 5XX error and you re-throw it, HubSpot retries the action for up to three days.

What problem custom code solves

HubSpot workflows can already set properties, create tasks, branch and delay. Custom code is for everything in between:

  • Calling an outside system. Look up a price, push a record into an ERP, fetch ad performance.
  • Calculating something. Score a lead, compute a renewal date, normalise a phone number.
  • Working across records. Read associated objects and summarise them.

The result can be written back to HubSpot directly, or returned as data outputs that later workflow actions use, for example in Edit records (HubSpot Developers).

A real example: ad performance into HubSpot

For a GroundTruth Ads integration, I built two daily workflows using HubSpot custom code actions and the CRM API:

  • Workflow 1 fetches all campaigns from GroundTruth and creates any new ones in HubSpot, with their configuration and performance data.
  • Workflow 2 runs 30 minutes later. It enrols each campaign individually and updates impressions, clicks, spend, CTR, visits, pacing and a computed status, including Expired detection.

Splitting "create new records" from "update each record" keeps each action small and focused. At ClearBox Strategies I also build custom-coded workflows to automate sales and marketing processes.

Before you start: the checklist

  1. Subscription. Data Hub Professional or Enterprise in the portal where the workflow runs.
  2. Access token. Store a token as a secret. HubSpot's docs use a private app access token, and the app must include the scopes for the data you touch, such as contacts or forms (HubSpot Developers). HubSpot now labels UI-created private apps as legacy private apps (HubSpot Developers — legacy private apps).
  3. Test record. Testing a custom code action runs the code for real and applies changes to the selected record, so use a dedicated test record.
  4. Know the limits (next section).

The limits that shape your design

All from HubSpot's custom code actions documentation (source):

LimitValue
Execution timeMust finish within 20 seconds
MemoryUp to 128 MB
Properties passed inUp to 50 per action
SecretsTotal length of all secret values ≤ 1,000 characters
String output65,000 characters (otherwise OUTPUT_VALUES_TOO_LARGE)
RuntimeNode.js on AWS Lambda; Python in beta
Test accountsUse OAuth or a private app token; developer API keys won't work

You can also rate limit the action itself: set a maximum number of executions per seconds, minutes or hours. That throttle also applies to the actions that come after it in the workflow.

Libraries you can require()

Available Node.js libraries include:

  • @hubspot/api-client (^10)
  • axios (^1.2.0)
  • lodash (^4.17.20)
  • async, bluebird and random-number-csprng
  • aws-sdk, googleapis, mongoose, mysql, redis and request

You can't install arbitrary npm packages, so check this list before you design around a library.

The pattern, step by step

  1. Automation → Workflows: open or create a workflow, click +, search for Custom code.
  2. Add your secret, for example HUBSPOT_TOKEN.
  3. Add properties to include in code. These arrive in event.inputFields, which saves an API call.
  4. Write the code.
  5. Define data outputs (type plus name) for anything later steps need.
  6. Test against a test record and check the status, outputs and logs.
  7. Save, then review action logs once the workflow is live.

Verified Node.js skeleton

This follows the structure of HubSpot's own code sample:

const hubspot = require('@hubspot/api-client');

exports.main = async (event, callback) => {
  const hubspotClient = new hubspot.Client({
    accessToken: process.env.HUBSPOT_TOKEN, // secret added in the action
  });

  // Value passed in via "Properties to include in code"
  const email = event.inputFields['email'];

  let phone;
  try {
    const res = await hubspotClient.crm.contacts.basicApi.getById(
      event.object.objectId,
      ['phone']
    );
    phone = res.properties.phone;
  } catch (err) {
    console.error(err);
    // Re-throw so HubSpot retries on rate-limit / 429 / 5XX errors
    throw err;
  }

  callback({
    outputFields: {
      email: email,
      phone: phone,
    },
  });
};

What the event object contains:

  • origin.portalId;
  • origin.actionDefinitionId;
  • object.objectType and object.objectId (the enrolled record);
  • inputFields;
  • a unique callbackId per execution.

Retries: how failures behave

If your code hits a rate-limit error, or a 429 or 5XX error from axios or @hubspot/api-client, and you throw it, HubSpot reattempts the action for up to three days. The first retry comes one minute after the failure, then intervals grow to a maximum gap of eight hours (HubSpot Developers).

Two practical consequences:

  • Make your code idempotent. A retried action may run the same logic again. Use upserts and unique IDs, not blind creates (HubSpot custom objects API — upsert).
  • Don't swallow errors you want retried. A catch that only logs will mark the action as done.

Two caveats people miss

  • Math.random isn't random enough here. HubSpot can enrol many records at the same time and seeds Math.random with the current time, so different executions can generate the same numbers. HubSpot suggests the random-number-csprng library instead.
  • Variables outside exports.main can be reused between executions. That's great for a reused connection and dangerous for per-record state. Keep anything record-specific inside exports.main.

Dates, outputs and Edit records

To update a datetime property from an output, return a UNIX timestamp in milliseconds. For a date property, also set the time to midnight UTC, for example with currentDate.setUTCHours(0,0,0,0). Check HubSpot's compatible source and target properties before mapping outputs in Edit records.

API limits still apply

Calls from custom code count like any other API call. For privately distributed apps:

  • Professional/Enterprise accounts get 190 requests per 10 seconds and 625,000 / 1,000,000 requests per day.
  • Search endpoints have their own stricter limits.

(HubSpot usage guidelines)

When not to use custom code

  • The external call regularly takes longer than ~20 seconds. Offload it to Make or n8n, or to your own service called through a webhook action. Check the webhook action's own requirements first (HubSpot KB).
  • Non-technical admins will maintain it. A native action or a visual tool may be kinder.
  • You don't have Data Hub Professional. Then you can't use it at all.

Sources

FAQ

Which HubSpot plan includes custom code actions? Data Hub Professional or Enterprise.

How long can a custom code action run? Up to 20 seconds, with at most 128 MB of memory.

Will HubSpot retry a failed custom code action? Yes, for rate-limit, 429 and 5XX errors that you re-throw. HubSpot retries for up to three days, with gaps of up to eight hours.

Can I use any npm package? No. Only the libraries HubSpot lists, such as @hubspot/api-client, axios and lodash, are available.

Let's find the manual work hiding in your HubSpot.

30 minutes. You show me how things work today; I'll tell you what I'd fix first and roughly what it takes — no pitch deck.

Almost always reply within 24 hours