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
- Subscription. Data Hub Professional or Enterprise in the portal where the workflow runs.
- 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
contactsorforms(HubSpot Developers). HubSpot now labels UI-created private apps as legacy private apps (HubSpot Developers — legacy private apps). - 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.
- Know the limits (next section).
The limits that shape your design
All from HubSpot's custom code actions documentation (source):
| Limit | Value |
|---|---|
| Execution time | Must finish within 20 seconds |
| Memory | Up to 128 MB |
| Properties passed in | Up to 50 per action |
| Secrets | Total length of all secret values ≤ 1,000 characters |
| String output | 65,000 characters (otherwise OUTPUT_VALUES_TOO_LARGE) |
| Runtime | Node.js on AWS Lambda; Python in beta |
| Test accounts | Use 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,bluebirdandrandom-number-csprngaws-sdk,googleapis,mongoose,mysql,redisandrequest
You can't install arbitrary npm packages, so check this list before you design around a library.
The pattern, step by step
- Automation → Workflows: open or create a workflow, click +, search for Custom code.
- Add your secret, for example
HUBSPOT_TOKEN. - Add properties to include in code. These arrive in
event.inputFields, which saves an API call. - Write the code.
- Define data outputs (type plus name) for anything later steps need.
- Test against a test record and check the status, outputs and logs.
- 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.objectTypeandobject.objectId(the enrolled record);inputFields;- a unique
callbackIdper 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
catchthat only logs will mark the action as done.
Two caveats people miss
Math.randomisn't random enough here. HubSpot can enrol many records at the same time and seedsMath.randomwith the current time, so different executions can generate the same numbers. HubSpot suggests therandom-number-csprnglibrary instead.- Variables outside
exports.maincan be reused between executions. That's great for a reused connection and dangerous for per-record state. Keep anything record-specific insideexports.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.
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
- HubSpot Developers — Custom code actions: https://developers.hubspot.com/docs/api-reference/latest/automation/workflow-actions/custom-code-actions
- HubSpot Developers — Legacy private apps: https://developers.hubspot.com/docs/apps/legacy-apps/private-apps/overview
- HubSpot Developers — Usage guidelines: https://developers.hubspot.com/docs/developer-tooling/platform/usage-guidelines
- HubSpot Developers — Custom objects API (upsert): https://developers.hubspot.com/docs/api-reference/latest/crm/objects/custom-objects/guide
- HubSpot KB — Webhooks in workflows: https://knowledge.hubspot.com/workflows/how-do-i-use-webhooks-with-hubspot-workflows
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.