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

ghl-api-extended

v0.3.1

Published

Typed wrapper around GoHighLevel's internal search-v2 endpoints (contacts, opportunities, appointments) — filter the way the GHL UI does, with date-range fetch helpers and its own OAuth token store.

Readme

ghl-api-extended

A drop-in replacement for @gohighlevel/api-client's HighLevel class — same constructor, every official route unchanged (.contacts, .opportunities, .calendars, .oauth, ...) — plus the internal search-v2 endpoints GHL's own UI uses for Contacts, Opportunities, and Appointments/Calendar filtering, which aren't in the public SDK or docs at all.

If you're already using @gohighlevel/api-client, switching is a one-line import change — everything you call today keeps working, you just gain the extra routes. You don't need @gohighlevel/api-client installed separately; this package depends on it internally.

- import { HighLevel } from '@gohighlevel/api-client';
+ import { HighLevel } from 'ghl-api-extended';

These search-v2 endpoints aren't in GHL's public API docs, so docs/filters-reference.md and the per-endpoint docs cover the field/operator behavior directly. Check those before filtering on anything not already covered by the fetch*ByDateRange helpers below.

Install

npm install ghl-api-extended

Usage

HighLevel works exactly like the official SDK class — construct it however you already do (private integration token, agency/location access token, your own SessionStorage) — with the new methods available directly on the instance:

import { HighLevel } from 'ghl-api-extended';

const ghl = new HighLevel({
  clientId: process.env.GHL_CLIENT_ID,
  clientSecret: process.env.GHL_CLIENT_SECRET,
  locationAccessToken, // however you already obtain it
});

// Official SDK routes, unchanged:
await ghl.contacts.getContact({ contactId });

// New: filter + auto-paginate the way the GHL UI's Contacts tab does.
const contacts = await ghl.fetchContactsByDateRange({
  locationId,
  startDate: '2026-01-01',
  endDate: '2026-01-31',
  filters: [{ field: 'tags', operator: 'contains', value: ['confirmed'] }],
});

const opportunities = await ghl.fetchOpportunitiesByDateRange({
  locationId,
  dateField: 'last_stage_change_date', // default: date_added
  startDate: '2026-01-01',
  endDate: '2026-01-31',
  filters: [{ field: 'pipeline_id', operator: 'eq', value: [pipelineId] }],
});

const appointments = await ghl.fetchAppointmentsByDateRange({
  locationId,
  startDate: '2026-01-01', // ranges over startTime by default
  endDate: '2026-01-31',
  filters: [{ field: 'appoinmentStatus', operator: 'eq', value: 'confirmed' }],
});

The fetch*ByDateRange methods auto-paginate to exhaustion, same as scrolling a filtered list in the GHL UI — no separate page-loop needed. Each accepts maxResults/maxPages/pageLimit if you want to bound that. For a single page, or full control over sort/pagination/aggregations, use ghl.searchContacts(...) / ghl.searchOpportunities(...) / ghl.searchAppointments(...) directly.

The same methods are also exported as standalone functions (searchContacts(client, params), fetchContactsByDateRange(client, params), ...) that take any HighLevel-compatible client as their first argument — useful if you'd rather keep constructing the official SDK's class yourself and only pull in this package's search functions.

No existing auth? Use the built-in OAuth flow

If you don't already have a token source, this package ships a self-contained one — a local JSON file token store, no external infra:

Copy .env.example to .env, fill in your GHL marketplace app's GHL_CLIENT_ID / GHL_CLIENT_SECRET / GHL_REDIRECT_URI, then:

npm run authorize

This opens the GHL OAuth consent screen, catches the redirect on a local server, and saves the company session to .tokens.json (gitignored). Location tokens are minted and cached automatically as you use them.

import { findMostRecentCompanyId, getAuthorizedLocationClient } from 'ghl-api-extended';

const companyId = await findMostRecentCompanyId();
const ghl = await getAuthorizedLocationClient({ companyId, locationId }); // a HighLevel instance

Filters

filters is an array of leaf filters ({ field, operator, value }) or groups ({ group: 'AND' | 'OR', filters: [...] }), nestable arbitrarily. Multiple entries at the top level are implicitly ANDed together — see docs/filters-reference.md for the full field/operator map per endpoint, including the traps (opportunities filter fields are snake_case and don't all mechanically match the response's camelCase names; contacts/appointments custom fields need a customFields.<key> dot-path).

Invalid fields are rejected client-side before the network call (GhlFilterFieldError); operator/value-shape errors from GHL itself are classified into GhlFilterOperatorError / GhlFilterValueError so you can branch on them instead of parsing message strings.

License

Apache-2.0