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

@followgate/js

v0.15.6

Published

FollowGate SDK - Grow your audience with every download. Require social actions (follow, repost) before users can access your app.

Readme

@followgate/js

Grow your audience with every download. Require social actions (follow, repost) before users can access your app.

FollowGate is inspired by Hypeddit (for musicians), but built for software developers and app creators.

Installation

npm install @followgate/js

Quick Start (Built-in Modal)

Just a few lines of code - no custom UI needed:

import { FollowGate } from '@followgate/js';

// Initialize once (e.g., in your app's entry point)
FollowGate.init({
  appId: 'your-app-id',
  apiKey: 'fg_live_xxx',
  userId: user.id, // Recommended for server-side verification
  // Target handle is configured in the Dashboard - no code needed!
  onComplete: () => {
    // User completed all actions - redirect to your app
    router.push('/dashboard');
  },
});

// Show the modal (that's it!)
FollowGate.show();

The SDK renders a beautiful, fully-functional modal with:

  • Username input step
  • Follow action with intent URL
  • Repost action (optional, configured in Dashboard)
  • Confirmation step with custom text support
  • X button to close/cancel (redirects to cancelUrl if configured)
  • Dark/light themes
  • All styling included (no CSS needed)

When to Show the Modal

// After sign-up (recommended)
const handleSignUpSuccess = () => {
  FollowGate.show();
};

// Or check if already unlocked
if (!FollowGate.isUnlocked()) {
  FollowGate.show();
}

Handling Success (Important!)

The onComplete callback is called ONLY after the user successfully completes all required actions. This is the right place to:

  • Create the user account in your database
  • Grant access to premium features
  • Redirect to your app
FollowGate.init({
  appId: 'your-app-id',
  apiKey: 'fg_live_xxx',
  // Handle configured in Dashboard!

  onComplete: async () => {
    // User completed all steps - NOW create the account!
    const user = FollowGate.getUser();

    await fetch('/api/create-account', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        xUsername: user?.username, // Their X/Twitter username
        platform: user?.platform, // 'twitter'
      }),
    });

    // Then redirect to your app
    router.push('/dashboard');
  },
});

Why this matters: Don't create accounts before onComplete is called. Users might close the modal without completing the actions. Only when onComplete fires, you know they actually followed/reposted.

Event Listeners for Fine-Grained Control

// Fired when a SINGLE action is completed (follow OR repost)
FollowGate.on('complete', (data) => {
  console.log('Action completed:', data);
  // { platform: 'twitter', action: 'follow', target: 'your_handle', username: 'their_username' }
});

// Fired when ALL actions are completed (same timing as onComplete)
FollowGate.on('unlocked', (data) => {
  console.log('Gate unlocked:', data);
  // { username: 'their_username', actions: [{ platform, action, target }, ...] }
});

Username Input (2 Methods)

The SDK needs the user's social media username to work. There are exactly 2 ways to provide it:

Method 1: User enters username manually (default) If no username is passed, the modal shows a Welcome step where the user types their username.

FollowGate.init({
  appId: 'your-app-id',
  apiKey: 'fg_live_xxx',
  // Handle configured in Dashboard!
});

FollowGate.show(); // Modal starts with username input step

Method 2: App passes username via code (skips Welcome step) If your app already knows the user's username (e.g. from your own auth system), pass it directly. The Welcome step is skipped entirely.

FollowGate.init({
  appId: 'your-app-id',
  apiKey: 'fg_live_xxx',
  twitter: {
    username: 'their_x_username', // skips Welcome step
  },
});

FollowGate.show(); // Modal starts directly with Follow step

Note: No Twitter API calls are made in either case. The SDK uses intent URLs only.

Configuration Options

FollowGate.init({
  // Required
  appId: 'your-app-id', // Your App ID from the dashboard
  apiKey: 'fg_live_xxx', // Your API key (starts with fg_live_ or fg_test_)

  // Twitter/X Configuration (all optional - configure in Dashboard!)
  twitter: {
    overrideHandle: 'other_handle', // Override Dashboard handle (highest priority)
    overridePostUrl: 'https://x.com/user/status/123', // Override Dashboard post URL for repost
    username: 'their_username', // Pre-fill user's X username (skips Welcome step)
  },

  // Callback when user completes all actions
  onComplete: () => {
    router.push('/dashboard');
  },

  // Optional
  userId: 'clerk_user_123', // User ID for per-user unlock status (recommended with auth)
  debug: false, // Enable console logging for debugging
  accentColor: '#6366f1', // Customize primary button color (hex)
  theme: 'dark', // 'dark' | 'light' - modal appearance
  apiUrl: 'https://...', // Custom API URL (for self-hosted)
  forceShow: false, // Always show modal, even if user already unlocked
});

Dashboard Configuration: Target handle, required actions, custom texts, redirect URLs, and skip options are all configured in the Dashboard and fetched automatically by the SDK.

Handle Priority:

  1. twitter.overrideHandle (explicit code override - highest priority)
  2. Dashboard targetHandle (default)

Handle & Target Formats

The SDK automatically normalizes usernames - you can use @username or username, both work:

| Platform | Action | Target Format | Example | | ------------ | ------ | ----------------------------------- | ------------------------------------ | | Twitter | follow | Username (without @) | lukasvanuden | | Twitter | repost | Post URL or Tweet ID | https://x.com/user/status/123 | | Twitter | like | Post URL or Tweet ID | https://x.com/user/status/123 | | Bluesky | follow | Handle (with or without @) | user.bsky.social | | Bluesky | repost | AT URI or post path | user.bsky.social/post/xyz | | LinkedIn | follow | Company name or prefixed identifier | company:anthropic or in:username |

API Reference

Modal Methods

// Show the FollowGate modal (async - fetches config first)
// If user is already unlocked, calls onComplete immediately
await FollowGate.show();

// Hide the modal programmatically
// Pass true to redirect to cancelUrl (if configured)
FollowGate.hide();
FollowGate.hide(true); // redirect to cancelUrl

// Check if user has completed all actions
FollowGate.isUnlocked(); // true | false

// Reset user's session (for testing)
FollowGate.reset();

// Clear only unlock status, keep user data (v0.13.0+)
FollowGate.clearUnlockStatus();

Advanced Usage

For custom UI implementations (when you don't want to use the built-in modal):

// Set username manually (@ is automatically removed)
FollowGate.setUsername('your_username'); // or '@your_username' - both work!

// Check if username is set
FollowGate.hasUsername(); // true | false

// Open intent URLs directly (opens in new window)
// For follow: target = username (without @)
await FollowGate.openIntent({
  platform: 'twitter',
  action: 'follow',
  target: 'lukasvanuden', // NOT @lukasvanuden
});

// For repost: target = tweet ID
await FollowGate.openIntent({
  platform: 'twitter',
  action: 'repost',
  target: '1234567890123456789', // Tweet ID from URL
});

// Mark action as completed (tracks locally + sends to API)
await FollowGate.complete({
  platform: 'twitter',
  action: 'follow',
  target: 'lukasvanuden',
});

// Mark gate as unlocked (user has completed all required actions)
await FollowGate.unlock();

// Get current user info
FollowGate.getUser();
// Returns: { username: 'their_username', platform: 'twitter' } | null

// Get completed actions
FollowGate.getCompletedActions();
// Returns: [{ platform: 'twitter', action: 'follow', target: 'lukasvanuden' }, ...]

// Get full unlock status
FollowGate.getUnlockStatus();
// Returns: {
//   unlocked: boolean,
//   username?: string,
//   completedActions?: CompleteOptions[]
// }

Server-Side Verification

When userId is set, completions are automatically saved to the server. You can verify users from your backend:

// Check if user is verified (server-side)
const isVerified = await FollowGate.isVerified();
// Returns: true | false

// Get full verification status
const status = await FollowGate.getVerificationStatus();
// Returns: {
//   verified: boolean,
//   userId: string,
//   completedAt?: string,  // ISO date string
//   actions?: [{ action: 'follow', target: 'lukasvanuden' }, ...]
// }

Use Case: Conditional Features

// In your app (e.g., Chrome extension, Electron app)
FollowGate.init({
  appId: 'your-app-id',
  apiKey: 'fg_live_xxx',
  userId: clerkUserId,
  // Handle configured in Dashboard!
});

const isVerified = await FollowGate.isVerified();

if (isVerified) {
  // User completed FollowGate - Full access
  enableAllFeatures();
} else {
  // User hasn't completed - Limited access
  enableTrialMode();
}

Event Listeners

FollowGate.on('complete', (data) => {
  console.log('Action completed:', data);
});

FollowGate.on('unlocked', (data) => {
  console.log('Gate unlocked:', data);
});

FollowGate.on('error', (error) => {
  console.error('Error:', error);
});

// Remove listener
FollowGate.off('complete', handler);

Supported Platforms

| Platform | Actions | | --------- | -------------------------- | | Twitter/X | follow, repost, like | | Bluesky | follow, repost, like | | LinkedIn | follow |

Framework Examples

Next.js (App Router)

// app/welcome/page.tsx
'use client';

import { FollowGate } from '@followgate/js';
import { useRouter } from 'next/navigation';
import { useEffect } from 'react';

export default function WelcomePage() {
  const router = useRouter();

  useEffect(() => {
    FollowGate.init({
      appId: process.env.NEXT_PUBLIC_FOLLOWGATE_APP_ID!,
      apiKey: process.env.NEXT_PUBLIC_FOLLOWGATE_API_KEY!,
      // Handle configured in Dashboard!
      onComplete: () => router.push('/dashboard'),
    });

    // Show modal if not already unlocked
    if (!FollowGate.isUnlocked()) {
      FollowGate.show();
    } else {
      router.push('/dashboard');
    }
  }, [router]);

  return <div>Loading...</div>;
}

With Clerk Auth

// app/welcome/page.tsx
'use client';

import { FollowGate } from '@followgate/js';
import { useRouter } from 'next/navigation';
import { useUser } from '@clerk/nextjs';
import { useEffect } from 'react';

export default function WelcomePage() {
  const router = useRouter();
  const { user } = useUser();

  useEffect(() => {
    if (!user) return;

    FollowGate.init({
      appId: process.env.NEXT_PUBLIC_FOLLOWGATE_APP_ID!,
      apiKey: process.env.NEXT_PUBLIC_FOLLOWGATE_API_KEY!,
      userId: user.id, // Per-user unlock status (recommended!)
      // Handle configured in Dashboard!
      onComplete: () => router.push('/dashboard'),
    });

    if (!FollowGate.isUnlocked()) {
      FollowGate.show();
    } else {
      router.push('/dashboard');
    }
  }, [user, router]);

  return <div>Loading...</div>;
}

Why use userId? Without it, unlock status is stored per-browser. If User A completes the gate on a shared computer, User B would also be "unlocked". With userId, each user has their own unlock status.

TypeScript

Full TypeScript support included:

import type {
  Platform,
  SocialAction,
  FollowGateConfig,
  TwitterConfig,
  CompleteOptions,
  UserInfo,
  UnlockStatus,
  VerificationStatus,
} from '@followgate/js';

Pricing

FollowGate is 100% free. No tiers, no limits, no billing. Use it for as many apps and users as you want.

Links

License

MIT