@lizardglobal/payload-discord-sync
v0.1.7
Published
Payload CMS plugin to sync Discord guilds, channels, members, and roles via discord.js
Downloads
726
Maintainers
Readme
@lizardglobal/payload-discord-sync
Payload CMS plugin that syncs Discord guilds, channels, members, and roles into Payload collections using discord.js.
[!WARNING] This plugin is still experimental. APIs, collection schemas, and behavior may change without a stable compatibility guarantee. Use in production with caution and pin versions deliberately.
Features
- Syncs Discord guilds, channels, members, and roles into Payload collections
- Triggers: admin UI, programmatic API, HTTP endpoint, or scheduled Payload jobs
- Scope syncs to all guilds the bot is in, or a subset of guild IDs
- Optional user linking (automatic and/or programmatic) to your auth/users collection
- Member email field (from OAuth — Discord bots cannot read emails from guild member lists)
- Typed config with collection slug/field overrides
Sync is on-demand or scheduled. The plugin does not listen to live Discord gateway events (
guildUpdate,channelUpdate, etc.).
Requirements
- Payload 3.x
- Node.js
^18.20.2 || >=20.9.0 - A Discord bot with the Server Members Intent enabled (for member sync)
Install
pnpm add @lizardglobal/payload-discord-sync discord.js
# or
npm install @lizardglobal/payload-discord-sync discord.jsDiscord bot setup
- Create an application in the Discord Developer Portal
- Create a bot and copy the bot token
- Under Bot → Privileged Gateway Intents, enable Server Members Intent
- Invite the bot to your guild(s) with permission to view channels and members
Quick start
import { buildConfig } from 'payload'
import { discordSyncPlugin } from '@lizardglobal/payload-discord-sync'
export default buildConfig({
// ...
plugins: [
discordSyncPlugin({
botToken: process.env.DISCORD_BOT_TOKEN!,
// Optional: omit to sync every guild the bot is in
guildIds: ['123456789012345678'],
linkUsers: {
collection: 'users',
},
schedule: {
enabled: true,
cron: '0 */6 * * *',
seedOnInit: true, // avoid in busy serverless environments
},
}),
],
})Configuration
| Option | Type | Description |
| --- | --- | --- |
| botToken | string | Discord bot token (required unless disabled) |
| disabled | boolean | Keep collections in the schema but skip sync, endpoints, and jobs |
| guildIds | string[] | Limit sync to these guilds; empty/omitted = all bot guilds |
| resources | object | Enable/disable or customize guilds, channels, members, roles |
| linkUsers | object | Link Discord members to your users collection |
| schedule | boolean \| object | Recurring sync via Payload jobs (cron, queue, seedOnInit, …) |
| admin.group | string | Admin sidebar group (default Discord) |
| admin.syncAction | boolean | Show Sync Discord on the guilds list (default true) |
| intents | number[] | Override discord.js gateway intents |
| onSyncComplete | fn | Callback after a successful sync |
| onSyncError | fn | Callback when a sync fails |
Resources
discordSyncPlugin({
botToken: process.env.DISCORD_BOT_TOKEN!,
resources: {
members: false, // skip this collection
guilds: {
slug: 'servers',
fields: ({ defaultFields }) => [
...defaultFields,
{ name: 'notes', type: 'textarea' },
],
},
},
})Linking users
discordSyncPlugin({
botToken: process.env.DISCORD_BOT_TOKEN!,
linkUsers: {
collection: 'users', // your users / auth collection slug
discordIdField: 'discordId', // injected on users (default)
userField: 'user', // relationship on discord-members (default)
autoLink: true, // link on sync when discordId matches
matchByEmail: false, // also match member.email → users.email
injectUserFields: true,
},
})Automatic: on member sync, if a user already has discordId (or email when matchByEmail is on), the member’s user relationship is set.
Programmatic (typical after Discord OAuth with identify + email):
import { linkDiscordUser, unlinkDiscordUser } from '@lizardglobal/payload-discord-sync'
await linkDiscordUser({
payload,
options: {
botToken: process.env.DISCORD_BOT_TOKEN!,
linkUsers: { collection: 'users' },
},
userId: req.user.id,
discordUserId: discordProfile.id,
email: discordProfile.email, // stored on matching guild members
})
await unlinkDiscordUser({
payload,
options: {
botToken: process.env.DISCORD_BOT_TOKEN!,
linkUsers: { collection: 'users' },
},
userId: req.user.id,
})Triggering syncs
Admin panel
Open Discord Guilds and click Sync Discord (queues a job on the discord-sync queue).
Programmatic
import { syncDiscord, queueDiscordSync } from '@lizardglobal/payload-discord-sync'
// Inline
await syncDiscord({
payload,
options: { botToken: process.env.DISCORD_BOT_TOKEN! },
input: {
guildIds: ['123'], // optional
resources: ['guilds', 'channels'], // optional
},
})
// Via jobs queue
await queueDiscordSync({
payload,
options: { botToken: process.env.DISCORD_BOT_TOKEN! },
input: { guildIds: ['123'] },
reschedule: true,
})HTTP
POST /api/discord-sync (authenticated admin/user session)
{
"guildIds": ["123"],
"resources": ["guilds", "channels", "members", "roles"],
"async": true
}Omit async or set false to run inline and return the result in the response.
Scheduled jobs
With schedule.enabled, the plugin registers a discordSync task and an autoRun entry on the discord-sync queue. Your app must process Payload jobs (via jobs.autoRun or a worker).
Seed the first job with schedule.seedOnInit: true, the admin button, or queueDiscordSync({ reschedule: true }).
Collections
| Resource | Default slug | Key fields |
| --- | --- | --- |
| Guilds | discord-guilds | discordId, name, icon, ownerId, memberCount |
| Channels | discord-channels | discordId, name, type, guild |
| Members | discord-members | discordId (guildId:userId), userDiscordId, username, email, user (if linkUsers), guild |
| Roles | discord-roles | discordId, name, permissions, guild |
Every document also has lastSyncedAt and a raw JSON snapshot from Discord.
Contributing
See CONTRIBUTING.md for local setup, PR guidelines, and how releases work.
License
MIT
