@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.
Maintainers
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/jsQuick 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
cancelUrlif 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 stepMethod 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 stepNote: 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:
twitter.overrideHandle(explicit code override - highest priority)- 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
