🟒HubSpot Developer Practical Textbook β€” 2026 Edition Developer Edition
Chapter 7 Β Β·Β  Workflow Extensions & Custom Code Actions

Workflow extension &
custom code action

Learn practical implementation patterns for extending HubSpot workflow automation with code using Custom Code Actions (Node.js), Serverless Functions, and Run Agent steps.

Operations Hub Professional+
Node.js 18 runtime
Time required: Approximately 90 minutes
7-1 Overall picture of workflow expansion

Organize three ways to extend HubSpot workflows with code.

functionExecution environmentMain usesRequired plan
Custom Code Action Node.js (in HubSpot) External API call/data conversion/conditional branching Ops Hub Pro+
Serverless Functions Node.js (in HubSpot) HTTP endpoint/webhook reception/form post-processing CMS Hub Pro+
Run Agent(BreezeοΌ‰ Breeze AI agent Data classification, summarization, and scoring using AI Ops Hub Pro+
7-2 Basics of Custom Code Action

Run Node.js code directly within your workflows and interact with external services and the HubSpot API.

⚑ Custom Code Action specifications

runtime:Node.js 18.x (npm package is @hubspot/api-client etc. are pre-installed)
timeout:20 seconds (if exceeded, the workflow will be treated as an error)
input:Configure contact property values ​​in the workflow event.inputFields Receive at
output:callback() can return property values ​​and pass them to subsequent steps.

Custom Code Action β€” Basic structure
// HubSpot's Custom Code Action execution environment // event object contains contact information const hubspot = require('@hubspot/api-client'); exports.main = async (event, callback) => { // ── Get input value ────────────────────── const { email, firstname, hs_object_id: contactId, } = event.inputFields; // ── HubSpot client (via secret) ── const client = new hubspot.Client({ accessToken: process.env.HUBSPOT_ACCESS_TOKEN, }); try { // ── External API call example ────────────── const response = await fetch( `https://api.external-service.com/user?email=${email}`, { headers: { 'Authorization': `Bearer ${process.env.EXTERNAL_API_KEY}` } } ); const data = await response.json(); // ── Return the output value with callback ────────── callback({ outputFields: { external_score: String(data.score), external_status: data.status, sync_timestamp: new Date().toISOString(), }, }); } catch (err) { // Return outputFields empty in case of error (workflow records as error) console.error('Custom code error:', err.message); callback({ outputFields: {} }); } };
Custom Code Action β€” Practical example: Notify Slack
const https = require('https'); exports.main = async (event, callback) => { const { firstname, lastname, email, dealname, amount } = event.inputFields; const message = { blocks: [ { type: 'section', text: { type: 'mrkdwn', text: `πŸŽ‰ *New deal: ${dealname}*\nPerson in charge: ${firstname} ${lastname} (${email})\nAmount: Β₯${Number(amount).toLocaleString()}`, }, }, ], }; await fetch(process.env.SLACK_WEBHOOK_URL, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(message), }); callback({ outputFields: { slack_notified: 'true' } }); };
Custom Code Action β€” Practical Example: Lead Scoring with AI
exports.main = async (event, callback) => { const { jobtitle, company, num_employees, hs_analytics_source, recent_conversion_event_name } = event.inputFields; // Scoring with Anthropic Claude API const res = await fetch('https://api.anthropic.com/v1/messages', { method: 'POST', headers: { 'x-api-key': process.env.ANTHROPIC_API_KEY, 'anthropic-version': '2023-06-01', 'content-type': 'application/json', }, body: JSON.stringify({ model: 'claude-haiku-4-5-20251001', max_tokens: 256, messages: [{ role: 'user', content: `Please return the score from 0 to 100 and the reason from the lead information below in JSON. Job type: ${jobtitle}, Company: ${company}, Number of employees: ${num_employees}, Source: ${hs_analytics_source}, Conversion: ${recent_conversion_event_name} Format: {"score": number, "reason": "reason"}`, }], }), }); const data = await res.json(); const parsed = JSON.parse(data.content[0].text); callback({ outputFields: { ai_lead_score: String(parsed.score), ai_score_reason: parsed.reason, }, }); };
⚠ Secret management: Confidential information such as API keys can be registered in HubSpot under Settings β†’ Private Apps β†’ Secrets. process.env.SECRET_NAME Please refer to. Don't write it directly into your code.
7-3γ€€Serverless Functions

An HTTP endpoint running on your HubSpot host. Used for form post-processing, webhook reception, and external collaboration.

🌐 Serverless Functions specifications

Endpoint:https://your-portal.hs-sites.com/_hcms/api/function-name
runtime:Node.js 18.x
timeout:10 seconds (must return response)
certification:Can be secured with HubSpot Cookie (logged in user) or API key
file:within the theme serverless/ placed in folder

serverless/contact-lookup.js
const hubspot = require('@hubspot/api-client'); exports.main = async (context, sendResponse) => { const { email } = context.params; if (!email) { return sendResponse({ statusCode: 400, body: { error: 'email is required' }, }); } const client = new hubspot.Client({ accessToken: process.env.HUBSPOT_ACCESS_TOKEN, }); const result = await client.crm.contacts.searchApi.doSearch({ filterGroups: [{ filters: [{ propertyName: 'email', operator: 'EQ', value: email }], }], properties: ['firstname', 'lastname', 'email', 'lifecyclestage'], limit: 1, }); if (result.results.length === 0) { return sendResponse({ statusCode: 404, body: { found: false } }); } sendResponse({ statusCode: 200, body: { found: true, contact: result.results[0].properties }, }); };
serverless.json β€” Function configuration file
{ "runtime": "nodejs18.x", "version": "1.0", "secrets": ["HUBSPOT_ACCESS_TOKEN"], "endpoints": { "contact-lookup": { "method": "GET", "file": "contact-lookup.js" }, "form-handler": { "method": "POST", "file": "form-handler.js" } } }
7-4 Run Agent step (Breeze AI cooperation)

Invoke the Breeze AI agent in your workflow to process contact data with AI.

πŸ€– What you can do with the Run Agent step

In the Run Agent step of the workflow, you can specify a Breeze AI agent and pass contact properties as input. Automatic generation of email text, classification of inquiry content, scoring, translation, summarizationEmbed AI processing such as as a no-code workflow step. Processing results can also be written back to contact properties.

πŸ’‘ Typical use cases for Run Agent

Lead classification:Automatically categorizes inquiries into ``interested in products,'' ``support issues,'' and ``recruitment inquiries.''
Personalization:AI automatically generates the text of sales emails based on contact attributes
Sentiment analysis:Calculate sentiment scores for support tickets and automatically prioritize them
translation:Automatically translate multilingual inquiries into Japanese and save in properties

How to use with Custom Code Action: Run Agent can be used by simply writing a prompt on the settings screen.no codeThis is the AI ​​step. On the other hand, Custom Code Action allows you to freely write logic in Node.js. Choose Run Agent for simple AI processing, or choose Custom Code Action for complex logic or external API combinations.
7-5 Summary of this chapter

βœ… Chapter 7 Checklist

  • Understood the difference and usage of Custom Code Action, Serverless Functions, and Run Agent.
  • Custom Code Action event.inputFields and callback(outputFields) learned how to use
  • External API calls, Slack notifications, and AI scoring can be implemented with Custom Code Actions
  • secret process.env can be managed securely via
  • You can implement HTTP endpoints with Serverless Functions
  • You can design AI processing using Run Agent steps.
About the next chapter (Chapter 8):Learn webhooks and event-driven design. We will explain Webhook signature verification, external system cooperation patterns, and idempotent processing.