@hoover-institution/hubspot-lib
v2.1.2
Published
A toolkit for deep integration with HubSpot's Marketing Events API with a plugin-based architecture.
Downloads
7
Readme
HubSpot SDK (hoover-institution/hubspot-lib)
This toolkit enables deep integration with HubSpot's Marketing Events API and introduces a flexible plugin system to customize behavior across the event lifecycle.
⚠️ This is version 2.0.0+ — includes breaking changes to method return structures. See Returning Plugin Information for details.
💡 Plugins are automatically cached after the first call to
loadPlugins(). You can reuse the same plugin map across your app without manually caching or exporting anything.
Table of Contents
- What's New
- Installation
- MarketingEvent Class
- Plugin System Overview
- Creating Inline Plugins
- Creating File-Based Plugins
- Native Plugins
- CLI: Generate Plugin Templates
- Full Testing Example with Plugins
- Feature Overview
What's New
This release introduces a full-featured plugin system that includes:
⚠️ Update 2.1.0. Some library methods pass additional context or data to plugins, allowing them to access more information about the operation being performed.
✅ Plugins can now return values. Each plugin handler may return a result (or error), and the SDK will return these values alongside the core HubSpot operation output. This allows plugins to report inserted IDs, custom status, debug info, and more.
🔁 MarketingEvent method return values have changed —
createEvent,deleteEvent, and others now return a{ hubspot, plugins }object instead of a flat response. This is a breaking change.⚡ Plugin loading is now cached by default — the first call to
loadPlugins()stores the plugin map. All future calls return the same result for consistency and performance. Manual caching is no longer required.🧩 Built-in
PLUGINS.ALLconstant — PassPLUGINS.ALLwhen creating aMarketingEventinstance to activate every loaded plugin automatically, without manually listing them.Inline plugins defined in code
File-based plugins loaded from disk
Bitmask-based lifecycle hooks for efficient execution
A CLI to scaffold plugin templates
Typed payloads and full IDE integration
Installation
npm install @hoover-institution/hubspot-libTo load file-based plugins, define the plugin directory in your consumer package.json:
"hubspot-lib": {
"pluginDir": "src/plugins"
}MarketingEvent Class
The MarketingEvent class provides the core API for creating, managing, and tracking events and their associated contacts. It is the heart of the SDK.
Constructor
new MarketingEvent(accountId, { plugins: [...] })accountId: Your HubSpot portal's account ID.plugins: A list of plugin identifiers from the PLUGINS object, such asPLUGINS.MY_PLUGIN
⚠️ You must call loadPlugins() before instantiating the MarketingEvent class. If you don't, the plugins will not be available.
Lifecycle Hook Events (EVENTS)
CREATE_EVENT;
GET_EVENT;
GET_EVENTS;
DELETE_EVENT;
REGISTER_EMAIL;
GET_CONTACTS_BY_STATE;
CREATE_OR_FIND_CONTACT_LIST;
GET_CONTACT_EVENT_STATE;
ADD_CONTACT_TO_LIST;
REMOVE_CONTACT_FROM_LIST;
ASSOCIATE_LIST_WITH_EVENT;
MARKETING_EVENT_ERROR;Methods
createEvent(payload)
Creates a marketing event. Triggers CREATE_EVENT plugins.
Returns: { hubspot: { objectId, status, ... }, plugins: [...] }
getEvent(externalEventId?)
Fetches the marketing event by external ID.
Returns: { objectId, eventName, ... } | null
deleteEvent(externalEventId?)
Deletes an event and triggers DELETE_EVENT plugins.
Returns: { hubspot: { success: boolean }, plugins: [...] }
createOrFindContactList(name)
Creates or returns an existing contact list by name.
Returns: { listId: string }
associateListWithEvent(objectId, listId)
Links a contact list to a marketing event.
Returns: { hubspot: { success: boolean }, plugins: [...] }
registerEmail(email, externalEventId, subscriberState [, fullName])
Adds a contact to the appropriate list. If a contact does not exist, it will automatically create one with the provided email and optional full name.
Returns: { hubspot: { status: string, listId: string }, plugins: [...] }
getContactEventState(email, externalEventId)
Gets the subscriber state for a contact on an event.
Returns: number (from SUBSCRIBER_STATE)
getSubscriberStateName(code)
Maps a subscriber state code to a label.
Returns: "REGISTERED" | "CANCELED" | "ATTENDED"
Plugin System Overview
Plugins can be attached to lifecycle hook points such as:
CREATE_EVENTGET_EVENTDELETE_EVENTREGISTER_EMAILMARKETING_EVENT_ERROR
Plugins can be either:
- Inline (defined directly in JS)
- File-based (loaded from a directory)
Plugin Payloads
Each hook receives a typed payload. For example:
[EVENTS.CREATE_EVENT]: ({ eventName, externalEventId, status }) => { ... }
Plugin Loading Options
Plugins are typically loaded once at startup and reused throughout the application.
You can load plugins in two ways:
1. Load specific plugins by name:
const PLUGINS = loadPlugins(["MY_PLUGIN"], { useDirectory: false });2. Automatically discover all inline and file-based plugins:
const PLUGINS = loadPlugins([], { useDirectory: true });By default, loadPlugins() caches the plugin map the first time it's called. Subsequent calls return the same result, regardless of names or useDirectory. To force a reload, pass { cache: false }.
💡 You can safely call
loadPlugins()multiple times in your server code — the system ensures plugins are loaded only once.
Returning Plugin Information
When a plugin function returns a value, the SDK automatically wraps that value in a structured result object as part of the main return payload. You do not need to return metadata like pluginId, pluginName, or success yourself — the system adds those for you.
Each plugin's result is included in the plugins array of the overall return value:
ℹ️ Avoid returning full HubSpot responses inside plugins — the core
hubspotfield already includes that. Plugin results should be minimal (status codes, flags, debug info).
{
hubspot: { ... },
plugins: [
{
pluginId: number,
pluginName: string,
success: boolean,
result?: any,
error?: string
},
...
]
}A plugin is considered success: true if it completes without throwing. If it throws an error, success will be false, and the error field will contain the message. You can return any custom shape from your plugin — it will be captured under result.
Example plugin return:
return {
pluginEventId: "abc-123",
statusNote: "Successfully synced to MongoDB",
};To indicate failure, use throw:
throw new Error("MongoDB insert failed.");This will be returned as:
{
success: false,
error: "MongoDB insert failed."
}If you return a shape like { ok: false }, but do not throw, the SDK will still treat the plugin as successful from an execution standpoint. Use this only for soft-failures or metadata.
Accessing Individual Plugin Results
You can access each plugin's return using the getPluginResults() utility, which converts the plugin array into a fast lookup map:
import { getPluginResults } from "@hoover-institution/hubspot-lib";
const result = await instance.createEvent(payload);
const pluginResults = getPluginResults(result.plugins);
// Access result of a specific plugin by name
const mongo = pluginResults[PLUGINS.MONGO_SYNC];
if (mongo?.success) {
console.log("✅ Mongo inserted:", mongo.result.pluginEventId);
} else {
console.error("❌ Mongo plugin failed:", mongo.error);
}💡 All plugin results are wrapped and indexed. Using
getPluginResults()helps you avoid.find()calls and access specific plugin outcomes bypluginId.
Creating Inline Plugins
import { createPlugin, EVENTS } from "@hoover-institution/hubspot-lib";
createPlugin("INLINE_PLUGIN", {
[EVENTS.CREATE_EVENT]: ({ eventName }) => {
console.log(`[INLINE_PLUGIN] Created: ${eventName}`);
return { note: "Event processed by INLINE_PLUGIN" };
},
});Creating File-Based Plugins
You must define your plugin directory in your consumer package.json:
"hubspot-lib": {
"pluginDir": "src/plugins"
}Then add files in that folder:
// src/plugins/MY_PLUGIN.js
import { EVENTS, createPlugin } from "@hoover-institution/hubspot-lib";
export default createPlugin("MY_PLUGIN", {
[EVENTS.DELETE_EVENT]: ({ externalEventId }) => {
console.log(`[MY_PLUGIN] Deleted event: ${externalEventId}`);
return { deleted: true };
},
});Native Plugins
Currently available native plugins:
LOG_TO_CONSOLE— Logs all lifecycle event activity to the console.ALL— Loads and registers all plugins found. Use this to automatically include every plugin you’ve added, without specifying each one individually.
CLI: Generate Plugin Templates
You can scaffold new plugins interactively using the built-in CLI.
Step 1: Configure CLI in package.json
"bin": {
"create-plugin": "./node_modules/@hoover-institution/hubspot-lib/lib/cli/hook-init.js"
}Step 2: Run the CLI
npx create-pluginThe CLI is interactive:
🚀 HubSpot Plugin Generator
? 📂 How do you want to select the plugin directory? (Use arrow keys)
❯ Browse folders
Manually enter path
? 🔌 Select your plugin directory: ./plugins
? 📁 Do you want to create a new subfolder? No
? 🪝 Plugin names (comma-separated, use ALL_CAPS)
📌 Usage: PLUGIN_1, PLUGIN_2
MY_CUSTOM_PLUGIN
✅ Created: plugins/MY_CUSTOM_PLUGIN.js
✅ Scaffolding completeFull Testing Example with Plugins
import dotenv from "dotenv";
dotenv.config();
import {
createPlugin,
EVENTS,
loadPlugins,
SUBSCRIBER_STATE,
getPluginResults,
} from "@hoover-institution/hubspot-lib";
import { MarketingEvent } from "@hoover-institution/hubspot-lib";
import { payload } from "./payload.js";
createPlugin("INLINE_FULL", {
[EVENTS.CREATE_EVENT]: ({ eventName }) => {
console.log(`[INLINE_FULL][CreateEvent] ${eventName}`);
return { inline: true };
},
[EVENTS.GET_EVENT]: ({ externalEventId, found }) => {
console.log(
`[INLINE_FULL][GetEvent] ID: ${externalEventId}, Found: ${found}`
);
},
[EVENTS.DELETE_EVENT]: ({ externalEventId, success }) => {
console.log(
`[INLINE_FULL][DeleteEvent] ID: ${externalEventId}, Deleted: ${success}`
);
},
[EVENTS.MARKETING_EVENT_ERROR]: ({ action, error }) => {
console.error(`[INLINE_FULL][Error] ${action}:`, error.message);
},
});
const PLUGINS = await loadPlugins([], { useDirectory: true });
const instance = new MarketingEvent(process.env.HUBSPOT_ACCOUNT_ID, {
plugins: [PLUGINS.TEST_PLUGIN, PLUGINS.LOG_TO_CONSOLE, PLUGINS.INLINE_FULL],
});
const found = await instance.getEvent(payload.externalEventId);
console.log(`Get event result: ${found ? "Found" : "Not Found"}`);
const result = await instance.createEvent(payload);
console.log("✅ Event created:", result.hubspot.eventName);
const pluginResults = getPluginResults(result.plugins);
const inline = pluginResults[PLUGINS.INLINE_FULL];
if (inline?.success) console.log("🔌 Inline plugin returned:", inline.result);
await instance.deleteEvent();
console.log("✅ Event deleted:", payload.eventName);payload.js
export const payload = {
eventName: "My Webinar",
eventOrganizer: "Dante Ielceanu",
externalAccountId: process.env.HUBSPOT_ACCOUNT_ID,
externalEventId: 1234567890,
startDateTime: new Date().toISOString(),
eventType: "WEBINAR",
url: "https://example.com/event",
};Feature Overview
| Feature | Status | | -------------------------- | ------ | | MarketingEvent Core API | ✔ | | Inline Plugins | ✔ | | File-Based Plugins | ✔ | | Bitmask Hook Engine | ✔ | | CLI Plugin Scaffolding | ✔ | | Full IDE Support via Types | ✔ |
