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

clickfunnels-sdk

v1.0.2

Published

Simple ClickFunnels 2.0 SDK - Add leads and check tag access

Readme

Unofficial ClickFunnels SDK by Wynter Jones

Simple, LLM-friendly Node.js SDK for ClickFunnels 2.0 - Add leads and check tag access

A lightweight SDK focused on the most common ClickFunnels operations: adding contacts with tags and checking if contacts have specific tags for access control.

Important: For backend use only, do not use client side.

Features

  • ✅ Add contacts with attributes and tags (by tag ID)
  • ✅ Check if contact has specific tags (by tag ID)
  • ✅ List teams, workspaces, and tags (to easily discover IDs)
  • ✅ TypeScript support with full type definitions
  • ✅ LLM-friendly with clear documentation
  • ✅ Automatic contact upsert (create or update)

Installation

npm install clickfunnels-sdk

Quick Start

const ClickFunnels = require("clickfunnels-sdk");

const cf = new ClickFunnels({
  apiKey: "your-api-key",
  workspaceId: 456,
  subdomain: "yourworkspace",
});

// List tags to get their IDs
const tags = await cf.listTags();
// [{ id: 1, name: 'Premium Member' }, { id: 2, name: 'Newsletter' }]

// Add contact with tag IDs
const result = await cf.addContact({
  email_address: "[email protected]",
  first_name: "John",
  last_name: "Doe",
  tag_ids: [1, 2], // Tag IDs
});

// Check if contact has tag (by ID)
const hasAccess = await cf.checkContactHasTag("[email protected]", 1);
if (hasAccess) {
  console.log("✅ User has access");
}

Getting Your Credentials

Step 1: Get API Key

  1. Go to ClickFunnels Team Settings → Developer Portal
  2. Create a Platform Application
  3. Copy your API Access Token

Step 2: Get Workspace ID and Subdomain

Use the SDK methods to discover your workspace configuration:

// Step 1: Create a temporary SDK instance with just your API key
const tempCf = new ClickFunnels({
  apiKey: "your-api-key",
  workspaceId: 0, // temporary
  subdomain: "temp", // temporary
});

// Step 2: Get your team ID
const teams = await tempCf.listTeams();
console.log(teams); // [{ id: 123, name: "My Team" }]

// Step 3: Get your workspace ID and subdomain using the team ID
const workspaces = await tempCf.listWorkspaces(123);
console.log(workspaces); // [{ id: 456, subdomain: "myworkspace" }]

// Step 4: Create your actual SDK instance with the correct values
const cf = new ClickFunnels({
  apiKey: "your-api-key",
  workspaceId: 456,
  subdomain: "myworkspace",
});

Step 3: Environment Variables

Create a .env file:

CLICKFUNNELS_API_KEY=your-api-key-here
CLICKFUNNELS_WORKSPACE_ID=456
CLICKFUNNELS_SUBDOMAIN=yourworkspace

Note: teamId is optional and only needed if you want to use listWorkspaces() without passing a teamId parameter.

API Reference

Constructor

new ClickFunnels(config);

Parameters:

  • apiKey (string, required): Your ClickFunnels API access token
  • workspaceId (number, required): Your workspace ID
  • subdomain (string, required): Your workspace subdomain
  • teamId (number, optional): Your team ID (only needed for listWorkspaces() helper)
  • userAgent (string, optional): Custom User-Agent header

listTeams()

List all teams accessible with your API key.

const teams = await cf.listTeams();
// Returns: [{ id: 123, name: "My Team", public_id: "abc123", ... }]

listWorkspaces(teamId?)

List all workspaces for a team.

// Provide teamId as parameter
const workspaces = await cf.listWorkspaces(123);
// Returns: [{ id: 456, name: "My Workspace", subdomain: "myworkspace", ... }]

// Or use teamId from config (if provided during initialization)
const cfWithTeam = new ClickFunnels({
  apiKey: "your-api-key",
  teamId: 123,
  workspaceId: 456,
  subdomain: "yourworkspace",
});
const workspaces2 = await cfWithTeam.listWorkspaces();

Note: Either teamId parameter or teamId in config is required for this method.

listTags()

List all tags in your workspace. Use this to get tag IDs!

const tags = await cf.listTags();
// Returns: [
//   { id: 1, name: "Premium Member", color: "#FF5733", ... },
//   { id: 2, name: "Newsletter", color: "#3498DB", ... }
// ]

addContact(attributes, tagIds?)

Add or update a contact with attributes and tags.

Parameters:

  • attributes (object): Contact information
    • email_address (string, required): Contact email
    • first_name (string): First name
    • last_name (string): Last name
    • phone_number (string): Phone number
    • time_zone (string): Timezone
    • website_url (string): Website URL
    • linkedin_url (string): LinkedIn profile URL
    • instagram_url (string): Instagram profile URL
    • twitter_url (string): Twitter profile URL
    • fb_url (string): Facebook profile URL
    • tag_ids (number[]): Array of tag IDs to apply
    • custom_attributes (object): Custom attributes as key-value pairs
  • tagIds (number[], optional): Array of tag IDs to apply (alternative to tag_ids in attributes)

Returns: Promise with contact and applied tags

const tags = await cf.listTags();
const premiumTagId = tags.find((t) => t.name === "Premium Member").id;

// Method 1: Using tag_ids in attributes (recommended)
const result = await cf.addContact({
  email_address: "[email protected]",
  first_name: "Jane",
  last_name: "Smith",
  phone_number: "+1234567890",
  website_url: "https://example.com",
  linkedin_url: "https://www.linkedin.com/in/janesmith",
  custom_attributes: { plan: "premium" },
  tag_ids: [premiumTagId], // Use tag ID, not name
});

// Method 2: Using separate tagIds parameter (backward compatible)
const result2 = await cf.addContact(
  {
    email_address: "[email protected]",
    first_name: "Jane",
  },
  [premiumTagId]
);

console.log("Contact ID:", result.contact.id);
console.log("Applied tags:", result.appliedTags);

checkContactHasTag(email, tagId)

Check if a contact has a specific tag (by tag ID).

Parameters:

  • email (string): Contact email to check
  • tagId (number): Tag ID to verify (get from listTags())

Returns: Promise - true if contact has tag, false otherwise

const tags = await cf.listTags();
const premiumTagId = tags.find((t) => t.name === "Premium Member").id;

const hasAccess = await cf.checkContactHasTag(
  "[email protected]",
  premiumTagId
);

if (hasAccess) {
  console.log("✅ User has premium access");
} else {
  console.log("❌ User does not have premium access");
}

Usage Examples

Example 1: Grant Access Based on Tag

// pages/api/verify-access.js (Next.js)
const ClickFunnels = require("clickfunnels-sdk");

const cf = new ClickFunnels({
  apiKey: process.env.CLICKFUNNELS_API_KEY,
  workspaceId: parseInt(process.env.CLICKFUNNELS_WORKSPACE_ID),
  subdomain: process.env.CLICKFUNNELS_SUBDOMAIN,
});

export default async function handler(req, res) {
  const { email_address } = req.body;

  // Get the Premium tag ID
  const tags = await cf.listTags();
  const premiumTag = tags.find((t) => t.name === "Premium Member");

  if (!premiumTag) {
    return res.status(500).json({ error: "Premium tag not found" });
  }

  // Check access using tag ID
  const hasAccess = await cf.checkContactHasTag(email_address, premiumTag.id);

  res.json({ access: hasAccess });
}

Example 2: Add Lead from Form Submission

// pages/api/signup.js (Next.js)
export default async function handler(req, res) {
  const { email, firstName, lastName } = req.body;

  // Get tag IDs
  const tags = await cf.listTags();
  const newLeadTag = tags.find((t) => t.name === "New Lead");
  const newsletterTag = tags.find((t) => t.name === "Newsletter");

  // Add contact with tags
  await cf.addContact({
    email_address: email,
    first_name: firstName,
    last_name: lastName,
    custom_attributes: {
      source: "website-signup",
      signup_date: new Date().toISOString(),
    },
    tag_ids: [newLeadTag.id, newsletterTag.id],
  });

  res.json({ success: true });
}

Example 3: Complete Workflow with Tag Lookup

async function onboardNewCustomer(email, plan) {
  // Get available tags
  const tags = await cf.listTags();

  // Find tag IDs by name
  const planTag = tags.find((t) => t.name === `${plan} Plan`);
  const activeTag = tags.find((t) => t.name === "Active Customer");

  if (!planTag || !activeTag) {
    throw new Error("Required tags not found");
  }

  // Add customer with tags
  const result = await cf.addContact({
    email_address: email,
    custom_attributes: {
      plan,
      onboarded_at: new Date().toISOString(),
    },
    tag_ids: [planTag.id, activeTag.id],
  });

  console.log("Customer onboarded:", result.contact.id);

  // Verify tags were applied
  const hasPlanTag = await cf.checkContactHasTag(email, planTag.id);
  const hasActiveTag = await cf.checkContactHasTag(email, activeTag.id);

  return {
    success: hasPlanTag && hasActiveTag,
    contactId: result.contact.id,
  };
}

Example 4: Middleware for Protected Routes

// middleware/requirePremium.js
async function requirePremiumAccess(req, res, next) {
  const email = req.user.email; // from your auth system

  const tags = await cf.listTags();
  const premiumTag = tags.find((t) => t.name === "Premium Member");

  const hasAccess = await cf.checkContactHasTag(email, premiumTag.id);

  if (!hasAccess) {
    return res.status(403).json({
      error: "Premium membership required",
    });
  }

  next();
}

TypeScript Support

Full TypeScript definitions are included:

import ClickFunnels, {
  ClickFunnelsConfig,
  Contact,
  Tag,
  AddContactResponse,
} from "clickfunnels-sdk";

const config: ClickFunnelsConfig = {
  apiKey: "your-api-key",
  workspaceId: 456,
  subdomain: "yourworkspace",
};

const cf = new ClickFunnels(config);

const tags: Tag[] = await cf.listTags();
const result: AddContactResponse = await cf.addContact({
  email_address: "[email protected]",
  tag_ids: [1, 2],
});

Testing

This SDK includes a comprehensive test suite to ensure reliability.

Running Tests

# Run all tests
npm test

# Run tests in watch mode (for development)
npm run test:watch

# Run tests with coverage report
npm run test:coverage

Test Coverage

The test suite includes 32 tests covering:

  • Constructor validation - Ensures all required config parameters are validated
  • listTeams() - Team listing and error handling
  • listWorkspaces() - Workspace listing with custom/default team IDs
  • listTags() - Tag listing with various response formats
  • addContact() - Contact creation, updating, and tag application
  • checkContactHasTag() - Tag verification and filtering
  • Error handling - API errors, timeouts, network failures
  • Edge cases - Empty responses, missing data, custom fields

Current coverage: ~92% (statements, branches, functions, lines)

Test Technologies

  • Jest - Testing framework
  • ts-jest - TypeScript support
  • axios-mock-adapter - HTTP request mocking

Tests run entirely offline using mocked HTTP requests, so no API credentials are required.

Important Notes

Tag IDs vs Tag Names

This SDK uses tag IDs (not tag names) for addContact() and checkContactHasTag(). This is by design because:

  1. Tag IDs are stable and unique
  2. Tag names can change in the ClickFunnels UI
  3. The ClickFunnels API uses IDs for applied tags

Always use listTags() first to get the tag IDs you need:

// ✅ CORRECT - Use tag ID
const tags = await cf.listTags();
const tagId = tags.find((t) => t.name === "Premium").id;
await cf.checkContactHasTag("[email protected]", tagId);

// ❌ WRONG - Don't use tag name
await cf.checkContactHasTag("[email protected]", "Premium"); // Won't work!

Contact Field Names

This SDK uses the official ClickFunnels API field names:

  • email_address (not email)
  • custom_attributes (not custom_fields)
  • tag_ids for applying tags within the contact object

These match the ClickFunnels 2.0 API specification exactly.

Contact Upsert Behavior

addContact() automatically handles create-or-update:

  • If email_address exists: Updates the contact's attributes
  • If email_address doesn't exist: Creates a new contact
  • Tags are always applied (won't duplicate if already applied)

Error Handling

The SDK throws ClickFunnelsError for API errors:

try {
  await cf.addContact({
    email_address: "[email protected]",
    tag_ids: [999],
  });
} catch (error) {
  if (error.name === "ClickFunnelsError") {
    console.error("Status:", error.statusCode);
    console.error("Message:", error.message);
    console.error("Details:", error.details);
  }
}

For AI/LLM Assistants

When using this SDK in AI coding assistants (Claude, Cursor, Replit, etc):

// 1. Install package
// npm install clickfunnels-sdk

// 2. Import and initialize
const ClickFunnels = require("clickfunnels-sdk");
const cf = new ClickFunnels({
  apiKey: process.env.CLICKFUNNELS_API_KEY,
  workspaceId: parseInt(process.env.CLICKFUNNELS_WORKSPACE_ID),
  subdomain: process.env.CLICKFUNNELS_SUBDOMAIN,
});

// 3. Always get tag IDs first
const tags = await cf.listTags();

// 4. Use tag IDs (not names) for operations
const tagId = tags.find((t) => t.name === "Your Tag Name").id;
await cf.addContact({
  email_address: "[email protected]",
  tag_ids: [tagId],
});
await cf.checkContactHasTag("[email protected]", tagId);

License

MIT

Support

For issues, questions, or contributions, please open an issue on GitHub.