npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@mohammadsaddam-dev/hubspot-toolkit

v2.0.0

Published

Opinionated lightweight HubSpot helper toolkit (contacts, companies, deals, associations, properties)

Downloads

388

Readme

@mohammadsaddam-dev/hubspot-toolkit

A lightweight and developer-friendly ES Modules toolkit for interacting with HubSpot CRM APIs.
Supports Contacts, Companies, Deals, Associations, Custom Properties, Search, Pagination, and clean utility helpers.

npm version npm downloads license license build

📚 Table of Contents


🚀 Features

✓ Contacts / Companies / Deals CRUD + Search
✓ Associations (V4 batch + simple helpers)
✓ Custom Properties API
✓ Pagination helper (fetchAllPages)
✓ Upsert helpers (contact by email, company by domain)
✓ Supports API Key (Private App Token) & Access Token
✓ Clean lightweight built-in HubSpot HTTP client
✓ Fully ESM ("type": "module")


Installation

npm install @mohammadsaddam-dev/hubspot-toolkit@latest

🔧 Setup

import { createClient } from "@mohammadsaddam-dev/hubspot-toolkit";

const hubspot = createClient({
  apiKey: process.env.HUBSPOT_API_KEY,   // or ACCESS TOKEN
  accessToken: process.env.HUBSPOT_ACCESS_TOKEN, 
});

If both are provided, accessToken is used.


Usage Examples

Contacts API

➤ Create Contact


await hubspot.contacts.create({
  email: "[email protected]",
  firstname: "John",
  lastname: "Doe",
});

console.log(response);

➤ Update Contact

await contacts.updateContact("12345", {
  firstname: "Updated Name",
});

➤ Get Contact by ID

const data = await contacts.get("12345");
console.log(data);

➤ Fetch All Contacts

const all = await hubspot.contacts.getAllContacts();
console.log("Total contacts:", all.length);

Deals API

➤ Create Deal

import { deals } from "@mohammadsaddam-dev/hubspot-toolkit";

await deals.create({
  dealname: "New Integration Deal",
  amount: 5000,
  dealstage: "appointmentscheduled",
});

Companies API

➤ Fetch All Companies

const companies = await hubspot.companies.getAllCompanies();
console.log(companies);

➤ Fetch Deal Properties

import { properties } from "@mohammadsaddam-dev/hubspot-toolkit";

const dealProps = await properties.get("deals");
console.log(dealProps);

Associations API

Manage relationships between HubSpot CRM objects using the CRM v4 Associations API.

This module is exposed via client.associations.

➤ Custom Object → Contact

await client.associations.associate(
  'p_tickets',
  ticketId,
  'contacts',
  contactId,
  'ticket_to_contact',
);

➤ Custom Object → Custom Object

await client.associations.associate(
  'p_orders',
  orderId,
  'p_tickets',
  ticketId,
  'order_to_ticket',
);


Associate Contact → Company

Create an association between a contact and a company.

await client.associations.associateContactToCompany(
  contactId,
  companyId
);

Custom Association Type

await client.associations.associateContactToCompany(
  contactId,
  companyId,
  'contact_to_company'
);

Associate Contact → Deal

Create an association between a contact and a deal.

await client.associations.associateContactToDeal(
  contactId,
  dealId
);

Associate Company → Deal

Create an association between a company and a deal.

await client.associations.associateCompanyToDeal(
  companyId,
  dealId
);

Get Associations

Retrieve associated records for an object.

const associations = await client.associations.getAssociations(
  'contacts',
  contactId,
  'deals'
);

| Name | Type | Description | | ---------- | -------- | -------------------------------------------------------------- | | fromType | string | Source object type (contacts, companies, deals, p_xxx) | | fromId | string | Source object ID | | toType | string | Target object type | | limit | number | Number of results (default: 100) | | after | string | Pagination cursor |


🧠 Design Notes

  • Uses CRM v4 batch association endpoints

  • One association per call (safe & explicit)

  • Supports standard and custom objects

  • Allows custom association types


⚠️ HubSpot Notes & Limitations

  • Association types must exist in HubSpot

  • Some object pairs require predefined types

  • Batch endpoints still allow single-item payloads

  • Pagination applies when fetching associations


🔧 Recommended Usage

  • Create associations immediately after record creation

  • Reuse known association types

  • Avoid hardcoding IDs inside business logic

  • Prefer batch endpoints for future scalability


Properties API

Manage HubSpot CRM properties (standard & custom) for contacts, companies, deals, and custom objects using the CRM v3 Properties API.

This module is exposed via client.properties.


➤ Creates a new custom property for a given object type.

await client.properties.createCustomProperty(
  'contacts',
  'external_id',
  'External ID',
  'string',
  'text',
  'contactinformation'
);

| Name | Type | Description | | ------------ | -------- | --------------------------------------------------------------- | | objectType | string | HubSpot object type (contacts, companies, deals, p_xxx) | | name | string | Internal property name (snake_case recommended) | | label | string | Human-readable label | | type | string | Data type (string, number, enumeration, etc.) | | fieldType | string | UI field type (text, select, textarea, etc.) | | groupName | string | Property group (default: contactinformation) | | options | array | Required for enumeration properties |


➤ Create an Enumeration Property (Example)

await client.properties.createCustomProperty(
  'deals',
  'deal_priority',
  'Deal Priority',
  'enumeration',
  'select',
  'dealinformation',
  [
    { label: 'Low', value: 'low' },
    { label: 'Medium', value: 'medium' },
    { label: 'High', value: 'high' },
  ]
);

➤ Update a Custom Property

Update an existing custom property.

await client.properties.updateCustomProperty(
  'contacts',
  'external_id',
  {
    label: 'External System ID',
    fieldType: 'text',
  }
);

⚠️ Not all property attributes are editable after creation.

HubSpot enforces restrictions depending on the property type.


➤ List Properties for an Object Type

Fetch all properties (standard + custom) for an object type.

const props = await client.properties.listCustomProperties('contacts');

🧠 Design Notes

Uses CRM v3 Properties API

Supports standard objects and custom objects

Keeps property creation explicit and predictable

Leaves validation to HubSpot (by design)


⚠️ HubSpot Limitations

Property name is immutable once created

Enumeration options cannot be removed if already in use

Some property fields are locked after creation


🔧 Recommended Usage

Create properties once during setup or migrations

Avoid dynamic property creation at runtime

Enforce naming conventions (snake_case)

Use consistent groupName across environments


Custom Objects API

const hubspot = createClient(cfg);
const tickets = hubspot.customObject('p_tickets');

await tickets.create({
  subject: 'Payment failed',
});

---
### 📘 API Reference
### create(props)

### Create a custom object record.
```js
const hubspot = createClient(cfg);
const tickets = hubspot.customObject('p_tickets');


const tickets = makeCustomObject(hubspotClient, 'p_tickets');

getById(id, properties?)

Fetch a record by ID.

const ticket = await tickets.getById('12345', [
  'subject',
  'priority',
]);

search(options)

Search records using HubSpot search filters.

const result = await tickets.search({
  filterGroups: [
    {
      filters: [
        {
          propertyName: 'priority',
          operator: 'EQ',
          value: 'high',
        },
      ],
    },
  ],
  properties: ['subject', 'priority'],
  limit: 10,
});


###fetchAll(options)

###Fetch all records using automatic pagination.

const allTickets = await tickets.fetchAll({
  properties: ['subject', 'priority'],
});

⚠️ HubSpot does not provide a “get all” endpoint.

This method internally paginates using /search.


update(id, props)

Update a record by ID.

await tickets.update('12345', {
  priority: 'low',
});

js

archive(id)

Archive (soft delete) a record.

await tickets.archive('12345');

upsert(uniqueProperty, uniqueValue, props)

Create or update a record using a unique property.

await tickets.upsert(
  'external_id',
  'TICKET-001',
  {
    subject: 'Payment failed',
    priority: 'high',
  }
);

How it works:

1. Searches for a record using the unique property

2. Updates if found

3. Creates if not found

⚠️ HubSpot does NOT support native upsert. This is the recommended and safest approach.

🧠 Design Decisions

Uses search-based pagination (HubSpot requirement)

Explicit upsert to avoid hidden behavior

Strict property normalization using toPropertiesObject

No assumptions about object schema

Client injected for testability

🛡️ Error Handling

This module:

Validates required inputs (id, objectType, unique fields)

Prevents invalid payloads

Relies on the client for:

     Retry logic

    Rate limit handling (429)

    Auth errors

⚠️ HubSpot Limitations to Know

Max search limit: 100

Search results are eventually consistent

Unique properties must be enforced in HubSpot

Nested objects are not allowed in property values


Project Structure

src/
├── client/
│   └── hubspotClient.js
├── crm/
│   └── makeCustomObject.js
│   └── contacts.js
│   └── deals.js
│   └── properties.js
│   └── associations.js
│   └── companies.js
├── utils/
│   └── toPropertiesObject.js
│   └── retry.js
│   └── errors.js
│   └── fetchAllPages.js
└── index.js

Environment Variables

HUBSPOT_API_KEY=your-private-app-key
HUBSPOT_ACCESS_TOKEN=your-private-access-token

Error Handling

try {
  await contacts.get("invalid-id");
} catch (err) {
  console.error("HubSpot Error:", err.message);
}

Hubspot Limitations

  • This toolkit is a thin abstraction over the HubSpot CRM APIs and therefore inherits the same platform limitations and behaviors.

  • API Rate Limits

  • HubSpot enforces strict rate limits per app and account

  • Excessive requests may result in 429 Too Many Requests

  • This toolkit does not retry automatically unless your HTTP client is configured to do so

Recommendation:

  • Add retry logic with exponential backoff and respect the Retry-After header.

  • Search & Pagination

  • HubSpot does not provide a “list all” endpoint

  • All bulk reads use the /search API with cursor-based pagination

  • Maximum search page size is 100 records

Impact:

  • Large datasets require multiple API calls

  • Results may be eventually consistent

  • Upsert Behavior

  • HubSpot does not support native upsert operations

  • The toolkit implements upsert as:

  • Search by a unique property

  • Update if found

  • Create if not found

Caveats:

  • Duplicate records may occur if uniqueness is not enforced in HubSpot

  • Search indexing delays can cause race conditions

  • Property Constraints

  • Property names are immutable once created

  • Some property attributes cannot be updated after creation

  • Enumeration options cannot be removed if already in use

  • Nested objects are not supported as property values

Associations

  • Association types must already exist in HubSpot

  • Some object pairs require predefined association types

  • Deleting an object automatically removes its associations

  • Custom Objects

  • Custom object schemas must be created in HubSpot before use

  • Object type names (p_xxx, 2-xxxxx) must be exact

  • Property and association availability depends on schema configuration

Error Handling

  • API errors are returned directly from HubSpot

  • Error messages and formats may change over time

  • The toolkit does not mask or normalize HubSpot errors by default

  • No Offline Validation

  • Invalid requests will fail at the HubSpot API level

🧠 Design Philosophy

  • This toolkit intentionally:

  • Avoids over-abstracting HubSpot behavior

  • Keeps API interactions explicit and predictable

  • Leaves retries, caching, and advanced validation to the consumer

🔮 Planned Improvements (Optional)

  • Automatic retry & rate-limit handling

  • Idempotent helpers (safe create/update)

  • Schema introspection utilities

  • Batch operation helpers


📝 License

MIT © Mohammad Saddam