patreon-connect-discord is a lightweight Node.js package that seamlessly connects Patreon and Discord, making it easy to automatically manage Discord roles based on your users' Patreon membership status.
Perfect for granting real-time premium access to your supporters!
-
🔥 Track Patreon Memberships
Monitor new subscriptions, cancellations, and payment statuses automatically. -
🔗 Discord Integration
Instantly retrieve linked Discord user IDs directly from Patreon. -
🛡️ Status Tracking
Easily track active, declined, and cancelled memberships in real-time. -
⚡ Event System
Clean, event-based architecture for effortless integration into your bot or app. -
💾 Persistent Cache
Smart caching system prevents duplicate events even after application restarts. -
📣 Webhook-Compatible
Easily integrate with Discord webhooks for instant membership notifications.
npm install patreon-connect-discord
Or with Yarn:
yarn add patreon-connect-discord
const { PatreonEvents } = require('patreon-connect-discord');
// Initialize with your credentials
const patreon = new PatreonEvents({
accessToken: 'your-patreon-access-token',
campaignId: 'your-campaign-id'
});
// Subscribe to events
patreon.on('ready', () => {
console.log('Patreon monitoring started!');
});
patreon.on('subscribed', (member) => {
console.log(`New patron: ${member.fullName} (${member.id})`);
console.log(`Discord ID: ${member.discordId || 'Not connected'}`);
});
// Start monitoring
patreon.initialize();
const patreon = new PatreonEvents({
// Required configuration
accessToken: 'your-patreon-access-token',
campaignId: 'your-campaign-id',
// Optional configuration
checkInterval: 60000, // How often to check for updates (ms), default: 60000 (1 minute)
cacheFile: './patreon-cache.json', // Custom cache file path
cacheSaveInterval: 300000 // How often to save cache (ms), default: 300000 (5 minutes)
});
Event | Description | Parameter |
---|---|---|
ready |
Emitted when monitoring has started | None |
subscribed |
Emitted when a new patron subscribes | Patron data object |
canceled |
Emitted when a patron cancels their subscription | Patron data object |
declined |
Emitted when a patron's payment is declined | Patron data object |
reactivated |
Emitted when a canceled patron reactivates | Patron data object |
connected |
Emitted when a patron connects their Discord account | Patron data object |
disconnected |
Emitted when a patron disconnects their Discord account | Patron data object |
expired |
Emitted when a membership expires | Patron data object |
error |
Emitted when an error occurs | Error object |
Each patron object contains:
{
id: string; // Patreon member ID
status: string; // Status: active_patron, declined_patron, former_patron
fullName?: string; // Patron's full name (if available)
email?: string; // Patron's email (if available)
patronStatus?: string; // Detailed patron status
pledgeAmount?: number; // Amount in dollars (if available)
discordId: string | null; // Discord user ID (if connected)
joinedAt?: string; // When they became a patron
expiresAt?: string; // When their current pledge expires
relationships?: any; // Raw relationships data from Patreon API
}
const { Client, GatewayIntentBits } = require('discord.js');
const { PatreonEvents } = require('patreon-connect-discord');
// Initialize Discord client
const client = new Client({
intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildMembers]
});
// Initialize Patreon events
const patreon = new PatreonEvents({
accessToken: 'your-patreon-access-token',
campaignId: 'your-campaign-id',
cacheFile: './patreon-cache.json'
});
// Set up role management functions
async function addPatronRole(discordId) {
const guild = client.guilds.cache.get('your-guild-id');
if (!guild) return;
try {
const member = await guild.members.fetch(discordId);
if (member) {
await member.roles.add('patron-role-id');
console.log(`Added patron role to ${member.user.tag}`);
}
} catch (error) {
console.error(`Failed to add role: ${error.message}`);
}
}
async function removePatronRole(discordId) {
const guild = client.guilds.cache.get('your-guild-id');
if (!guild) return;
try {
const member = await guild.members.fetch(discordId);
if (member) {
await member.roles.remove('patron-role-id');
console.log(`Removed patron role from ${member.user.tag}`);
}
} catch (error) {
console.error(`Failed to remove role: ${error.message}`);
}
}
// Handle Patreon events
patreon.on('ready', () => {
console.log('Patreon monitoring started!');
});
patreon.on('subscribed', (member) => {
console.log(`New patron: ${member.fullName}`);
if (member.discordId) {
addPatronRole(member.discordId);
}
});
patreon.on('connected', (member) => {
console.log(`Patron connected Discord: ${member.discordId}`);
addPatronRole(member.discordId);
});
patreon.on('canceled', (member) => {
console.log(`Patron canceled: ${member.fullName}`);
if (member.discordId) {
removePatronRole(member.discordId);
}
});
patreon.on('declined', (member) => {
console.log(`Patron payment declined: ${member.fullName}`);
if (member.discordId) {
removePatronRole(member.discordId);
}
});
patreon.on('disconnected', (member) => {
console.log(`Patron disconnected Discord: ${member.discordId}`);
removePatronRole(member.discordId);
});
// Start both systems
client.once('ready', () => {
console.log(`Logged in as ${client.user.tag}`);
patreon.initialize();
});
client.login('your-discord-bot-token');
const { PatreonEvents } = require('patreon-connect-discord');
const patreon = new PatreonEvents({
accessToken: 'your-patreon-access-token',
campaignId: 'your-campaign-id'
});
// Initialize and wait for ready event
patreon.on('ready', () => {
// Now you can look up patrons by Discord ID
const checkPatronStatus = (discordId) => {
const patron = patreon.users.get(discordId);
if (patron) {
console.log(`Found patron: ${patron.fullName}`);
console.log(`Status: ${patron.status}`);
console.log(`Pledge amount: $${patron.pledgeAmount || 'unknown'}`);
return true;
} else {
console.log(`No patron found with Discord ID: ${discordId}`);
return false;
}
};
// Example usage
checkPatronStatus('559253955230695426');
});
patreon.initialize();
The package includes a robust caching system that:
- Prevents duplicate events across application restarts
- Tracks Discord ID connections and disconnections
- Maintains a history of membership status changes
This ensures your application won't send duplicate welcome messages or assign roles multiple times.
// Configure with a cache file
const patreon = new PatreonEvents({
accessToken: 'your-patreon-access-token',
campaignId: 'your-campaign-id',
cacheFile: './data/patreon-cache.json' // Custom location
});
// The cache will be saved automatically and loaded on restart
To use this package, you need:
- A Patreon Creator account
- A Patreon API Client (create one at https://www.patreon.com/portal/registration/register-clients)
- An access token with the following scopes:
identity
identity[email]
campaigns
campaigns.members
campaigns.members.address
campaigns.members[email]
Patreon has API rate limits. To avoid hitting these limits:
- Use a reasonable
checkInterval
(60000ms or higher recommended)
To ensure the cache is saved properly before your application exits:
// Handle graceful shutdown
process.on('SIGINT', () => {
console.log('Shutting down...');
patreon.stop(); // This saves the cache and cleans up
process.exit(0);
});
- Your access token may be invalid or expired
- Ensure you have the required scopes enabled for your token
- Check that you've called
initialize()
after setting up event listeners - Verify your campaign ID is correct
- Ensure your access token has the necessary permissions
- Confirm that your patrons have connected their Discord accounts to Patreon
- Ensure your access token has the
identity
scope