🟡 HubSpot Operations Practical Textbook — 2026 Edition
Chapter 3

programmable automation
JavaScript / Python Extend your workflow with

There are some processes that no-code workflow actions can't reach: complex calculation logic, communication with external APIs, conditional judgments that combine multiple properties, and unique business rules that are not included in HubSpot's standard actions. In this chapter How custom code actions work - How to use JavaScript and Python (Beta) - 10 code samples that frequently occur in practice - Security and error handling - Best practices for production operationsExplain systematically.

📖 Estimated reading time: 30 minutes
🎯 Target audience: HubSpot administrators, RevOps engineers, and developers
🔧 Required plan: Operations Hub Professional or higher

📋 Contents of this chapter

  1. 3-1How custom code actions work and the differences between JavaScript and Python
  2. 3-2Basic syntax and reading and writing of properties (JavaScript edition)
  3. 3-3Python Support (Beta)——Introduction and Basic Patterns
  4. 3-410 frequently occurring patterns in practice—scoring, API linkage, date calculation, etc.
  5. 3-5Best practices for error handling, security, and production operations
Section 3-1

How custom code actions work and the differences between JavaScript and Python

Custom Code Actions allow you to embed JavaScript or Python code blocks in your workflow.Ability to run on HubSpot's serverless environment (equivalent to AWS Lambda)is. Advanced processing can be achieved by simply writing code on the HubSpot portal's workflow UI without preparing an external server.

⚙️ Execution architecture for custom code actions
🔔
workflow
Trigger condition is met
📥
Input settings
Pass property value to variable
💻
code execution
JS/Python (serverless)
📤
Output writeback
Update HubSpot properties
➡️
Next action
WF continuation/branch
The execution environment is a HubSpot managed serverless platform (no external server required). Timeout limit: 20 seconds (JS) / 30 seconds (Python)

JavaScript vs Python (Beta)—which one to choose?

Comparison itemsJavaScript(Node.js)Python(Beta)
Availability ✓ GA (official release/stable) △ Beta (from 2025, please be careful when using it in production)
Node/Python version Node.js 18.x Python 3.9
standard library Node.js standard modules (https, crypto, etc.) Python standard library + requests, pandas, numpy
Applications suitable for API integration/JSON manipulation/string processing/asynchronous processing Data conversion, statistical calculation, data processing using pandas
Recommended users Front-end engineer/RevOps general Data Engineer/Analyst/Python Experienced
Recommended Winner 20 seconds 30 seconds
secrets (environment variables) ✓ Compatible ✓ Compatible
💡 Recommended as of 2026: JavaScript as the basis, Python limited to numerical calculations

Python support is still in Beta, so JavaScript is recommended for production-critical workflows.Choose Python only if you have specific needs such as processing large amounts of numerical data with pandas or linking with machine learning models.However, troubleshooting will be easier if you use JavaScript for everything else.

Section 3-2

Basic syntax and reading and writing of properties (JavaScript edition)

JavaScript for custom code actions has a fixed structure.What is Data Studio? Differences from ETL tools and architectureThere are three steps. The starting point is to fully understand this basic pattern.

JavaScript Basic structure template
// =================================================== // HubSpot custom code action — basic template // =================================================== // Required: exports.main is the execution entry point exports.main = async (event, callback) => { // --- INPUT: Get values ​​from workflow inputFields --- const email = event.inputFields['email']; const firstname = event.inputFields['firstname']; const company = event.inputFields['company']; const contactId = event.object.objectId; // ID of the record // --- PROCESS: Implementing business logic --- const domain = email.split('@')[1]; // Extract domain from email const greeting = `${firstname} Mr,${company} Person in charge of; // --- OUTPUT: Write results to outputFields --- callback({ outputFields: { email_domain: domain, // write to custom property greeting_text: greeting } }); };

How to set Input / Output (UI side operation)

Before writing the code, on the workflow editorDeclare "Input Fields" and "Output Fields" in advanceSet a calendar reminder to check the expiry date of your API key/credentials every quarter

Setting itemsplaceSetting detailsPoints to note
Input Fields Code editor left panel “Inputs” Select the properties you want to pass to your code (e.g. email, company, custom_score) Undeclared properties cannot be retrieved with event.inputFields
Output Fields Code editor left panel "Outputs" Mapping variable name (e.g. total_score) and write destination property (e.g. custom property "total_score") The key name of outputFields in the code and the variable name of the Output declaration must match exactly.
Secrets (environment variables) Settings → Private App / Secrets Register confidential information such as API key with "secret name" and refer to it in the code with process.env.secret name Don't write API keys directly in your code. It will be leaked the moment you push it to GitHub.
Section 3-3

Python Support (Beta)——Introduction and Basic Patterns

Python support, available starting in 2025, is a great addition for data engineers and analysts.Pre-installed libraries such as pandas, numpy, requests, etc.It is possible to convert CSV data and perform aggregation using data frames directly within the workflow.

Python Basic structure template (Beta)
# =================================================== "Skip" filter for synchronization log → aggregation by reason # =================================================== import os import json import requests # Pre-installed # Required: main function is the execution entry point def main(event): # --- INPUT --- email = event["inputFields"].get("email", "") score = event["inputFields"].get("lead_score", 0) contact_id = event["object"]["objectId"] # --- PROCESS --- domain = email.split("@")[-1] if "@" in email else "" tier = "High" if int(score) >= 80 else ("Mid" if int(score) >= 50 else "Low") # --- OUTPUT --- return { "outputFields": { "email_domain": domain, "lead_tier": tier } }
JavaScript (Node.js 18)
Execution limits and specifications
timeout20 seconds
memory limit128 MB
Execution limit (month)100,000 times (Pro) / Unlimited (Ent)
External HTTP communicationYes (https module/fetch)
file systemCannot read (only /tmp can be written)
npm packageNot possible (standard module only)
Python 3.9(Beta)
Execution limits and specifications
timeout30 seconds
memory limit256 MB
Execution limit (month)100,000 times (Pro) / Unlimited (Ent)
External HTTP communicationYes (requests library)
pre-installedrequests, pandas, numpy, json
pip installNot possible (preinstalled only)
Section 3-4

10 frequently occurring patterns in practice—scoring, API linkage, date calculation, etc.

Below is a summary of the most commonly used custom code action patterns in actual HubSpot operations. Each pattern is a practical sample that can be copied and used as is.

pattern 1
Composite scoring calculation
Weight multiple properties (industry score, company size score, engagement score), add them up, and write them to the custom score property. Supports complex weighting calculations that are difficult with no-code "contact scores."
When to use: When you want to implement your own ICP scoring model or change the weighting coefficients for each industry
pattern 2
Inquiry to external API (enrichment)
Send an HTTP request to an enrichment API such as Clearbit, ZoomInfo, or Apollo, and write the returned industry, company size, LinkedIn URL, etc. to the HubSpot property.
When to use: When you want to integrate with APIs not covered by HubSpot native enrichments
pattern 3
Date calculation/calculation of elapsed days
Calculate "how many days have passed since the contract start date", "how many days until the end of the trial", and "how many days since the last purchase date until today" and write them in the properties. Combine with WF's "days later" trigger to achieve timely actions.
When to use: Churn prediction, renewal reminder, upsell timing detection
pattern 4
Direct notifications to Slack
POST to the Incoming Webhook URL to send a structured message to a specific channel if you need more control than HubSpot's native Slack integration. Achieve rich notifications that include project amount, person in charge, and link.
When to use: Large project alerts, contact creation notifications with specific conditions, daily summary sending
pattern 5
Text normalization/formatting
We have implemented complex text conversions that cannot be handled by no-code "data formatting", such as name title removal ("Mr. Yamada" → "Yamada"), full-width half-width conversion, company name estimation from URL, and industry classification from email domain.
High cost ($50~/user/month)
pattern 6
Cross-record updates using the HubSpot API
Get all affiliated companies, deals, and tickets associated with a contact and update them all at once. Standard actions can only operate on one record, but related records can be processed together via the API.
Best use: When you want to close all related deals/tickets at once when a customer cancels.
pattern 7
Parsing and expanding JSON properties
Parse the JSON string property sent from the external system and expand each field into a separate property. Webhook Used to structure received data and decompose structured data sent from ERP.
When to use: When handling data sent in JSON from no-code tools such as Zapier or Make
pattern 8
Round robin agent assignment
Determine the next person in charge based on the number of contacts, number of cases handled, and territory, and set the contact owner. You can implement more complex assignment rules (shifts, skills, regions) than HubSpot's standard raw routing WF.
When to use it: Fair allocation of sales teams and proper routing of leads that require specialized skills
pattern 9
Verification with external DB/spreadsheet
Acquires information from Google Sheets, Airtable, and internal DB via external API, compares it with HubSpot's contact/company information, and updates the difference. Used for real-time syncing with information sources not connected to HubSpot.
When to use: Refer to price list, check inventory, check consistency with internal master data
pattern 10
Conditional setting of multiple properties at once
Implement complex IF-ELSE logic (set plan recommendation, team in charge, and SLA all at once based on a combination of industry, size, and region) in code to update multiple properties at once.
When to use: Simultaneous setting of multiple fields based on segments, classification based on ICP matrix

Pattern 1: Composite scoring implementation example

JavaScript Composite scoring calculation (industry/scale/engagement)
exports.main = async (event, callback) => { // Input Fields: industry, num_employees, hs_email_open_count, num_contacted_notes const industry = event.inputFields['industry'] || ''; const employees = parseInt(event.inputFields['num_employees'] || 0); const emailOpens = parseInt(event.inputFields['hs_email_open_count'] || 0); const contactedNotes= parseInt(event.inputFields['num_contacted_notes'] || 0); // ① Industry score (0-30 points) const industryScores = { 'Technology': 30, 'Finance': 28, 'Healthcare': 25, 'Manufacturing': 20, 'Retail': 15, 'Education': 10 }; const industryScore = industryScores[industry] || 5; // ② Company size score (0 to 30 points) let sizeScore = 0; if (employees >= 1000) sizeScore = 30; else if (employees >= 200) sizeScore = 22; else if (employees >= 50) sizeScore = 15; else if (employees >= 10) sizeScore = 8; // ③ Engagement score (0-40 points) const engagementScore = Math.min(40, (emailOpens * 3) + (contactedNotes * 5)); // ④ Total score and tier classification const totalScore = industryScore + sizeScore + engagementScore; const tier = totalScore >= 70 ? 'A' : totalScore >= 45 ? 'B' : 'C'; callback({ outputFields: { icp_score: totalScore, // Custom property: icp_score (number) icp_tier: tier // Custom property: icp_tier (string) } }); };

Pattern 4: Slack webhook notification implementation example

JavaScript Slack Incoming Webhook Notification (Large Issue Alert)
const https = require('https'); exports.main = async (event, callback) => { const dealName = event.inputFields['dealname']; const amount = event.inputFields['amount']; const ownerName = event.inputFields['hubspot_owner_id']; const dealId = event.object.objectId; // Refer to the webhook URL registered in Secrets (do not write it directly in the code!) const webhookUrl = process.env.SLACK_DEALS_WEBHOOK_URL; const payload = { text: `🎯 Large project alert`, blocks: [ { type: "section", text: { type: "mrkdwn", text: `*${dealName}* is now Closed Won! \n 💰 Amount: ¥${Number(amount).toLocaleString()}\n 👤 Responsible for:${ownerName}\n 🔗 <https://app.hubspot.com/deals/${dealId}|Check on HubSpot>` } } ] }; // Send to Slack with HTTPS POST await new Promise((resolve, reject) => { const body = JSON.stringify(payload); const url = new URL(webhookUrl); const req = https.request({ hostname: url.hostname, path: url.pathname, method: 'POST', headers: { 'Content-Type': 'application/json' }}, (res) => res.on('end', resolve)); req.on('error', reject); req.write(body); req.end(); }); callback({ outputFields: { slack_notified: 'true' } }); };
Section 3-5

Best practices for error handling, security, and production operations

Custom code actions are powerful, butWhen an error occurs, the entire workflow stops, confidential information is leaked, and execution costs explode.There are also risks involved. Be sure to review the following best practices before deploying to production.

⚠️ Common error patterns and solutions
5 errors you should be aware of before going live
timeout error
Error: Task timed out after 20.00 seconds
This occurs when the external API response is slow or the loop processing becomes an infinite loop. Solution: Set a timeout (10 seconds or less) for API calls. Always set a termination condition for the loop, and set it as an upper limit for the maximum number of iterations (for example, 100 times).
Null / Undefined error
TypeError: Cannot read property 'split' of null
Occurs when the code is executed on a record where the property declared in Input Fields is blank. Workaround: Set default values ​​for all input values ​​using "|| ''" or "|| 0". Always check for null before processing.
API rate limit error
Error: 429 Too Many Requests
Occurs when a large number of requests are made to external APIs (Clearbit, ZoomInfo, HubSpot's own API, etc.) in a short period of time. Solution: Insert a ``wait 1 second before code action'' delay action into the workflow. Avoid bulk apply, which performs code actions on large numbers of records at once.
JSON parse error
SyntaxError: Unexpected token in JSON
JSON.parse() fails when external API returns something other than JSON (such as an HTML error page). Workaround: Surround JSON.parse() with try-catch. Check the status code of the response and then parse it.
Secret not set error
TypeError: Cannot read property of undefined (process.env.API_KEY)
Occurs when referencing an environment variable that is not registered in Secrets. Solution: Register all necessary secrets in Settings → Private App Tokens / Secrets before deploying the code. Unify the secret name between development and production.

Security rules (must be followed)

ruleBad example (NG)Good example (OK)
Manage API keys Directly in the code:
const key = "sk-abc123..."
Register and see Secrets:
process.env.OPENAI_KEY
HubSpot API token Use hard-coded or wide-privileged Private App Tokens Create a dedicated Private App Token with only the scopes you need and store it in Secrets
Error log contents Output secret information to the log with console.log(apiKey) Workflow “Fill Smart Property” action
External communication destination Incorporating user input values ​​directly into the URL (SSRF risk) Correspondence URLs must be hard-coded or checked against allowlists before use.

Best practices for production operations

① Always conduct tests in a sandbox——It is strictly prohibited to suddenly run code on the production portal. First check the operation in HubSpot's sandbox (Developer Sandbox) and then apply it to production.

② Add a comment to the WF name/code——Leave a comment saying ``Who wrote this logic, when, and why?'' I will forget it myself after 3 months. It becomes unreadable when another administrator takes over.

③ Check the execution log regularlyBinary data is not synchronized. Sync URL only

④ Pay attention to the upper limit on the number of executions (100,000 times/month)——Professional plan allows you to run custom code actions up to 100,000 times per month. When applying to a large number of records at once, check the number of records in advance. You can check the remaining number of times in "Settings → Usage Status".

✅ Checklist before implementing custom code actions

① Have you declared Input / Output Fields on the UI? / ② Have you set null checks and default values for all input values? / ③ Have you registered API keys and other confidential information in Secrets and referenced them in process.env? / ④ Have you handled errors with try-catch? / ⑤ Have you obtained the intended results by running the test in a sandbox? / ⑥ Did you try just one case using the "Test Contact" function of the workflow before applying it to production? Clear all 6 items before deploying to production.

📌 Chapter 3 Summary

Custom code is the last resort to overcome the “no-code limit”

Complex calculations, external API communication, and conditional branching of multiple properties that cannot be handled with standard actions can be implemented in code. However, first check whether it can be achieved using standard actions before using it. Avoid abuse due to high maintenance costs.

Use JavaScript (GA) for production, Python (Beta) on a limited basis

JavaScript is stable and can be used in production. Python is used only in cases where numerical calculations using pandas are required. Because it is a beta feature, use Python in critical workflows only after understanding the risks. Be aware of timeout and memory limits for both.

Never write the API key directly—be sure to register it in Secrets

Hardcoding API keys into your code is the biggest security risk. The code is visible to all workflow administrators. All confidential information should be registered in Secrets and referenced in process.env.XXX. This is not a "recommendation" but an "absolute rule".

Create a service account specifically for HubSpot instead of a human account. Generate a strong random string of characters for your password.

HubSpot always has records with blank properties. If you process it as null/undefined, a TypeError will occur and the entire workflow will stop. Don't forget to set default values ​​for all input values ​​and try-catch external API calls.

HubSpot side

A bug in a custom code action could cause it to write incorrect values ​​to tens of thousands of records at the moment of bulk application. Be sure to check the operation in the sandbox, and in the actual production, expand it step by step from 1 item to 10 items to 100 items, confirm the operation, and then apply it to all items.

Design with an upper limit of 100,000 executions/month in mind

Calculate ICP score based on imputed values

Next Chapter
Chapter 4: Data Studio——Integrate and transform data with no code →