dis-backups
v4.1.1
Published
discord server backup framework, discord.js v14, bun-first
Maintainers
Readme
dis-backups
Discord server backup framework. discord.js v14.20+, Bun-first since v4.1.
Forked from Androz2091/discord-backup. Runs on Node 20+ too but faster on Bun.
Setup
bun add dis-backups
# or
npm install dis-backupsNeeds Node 20+ minimum. Bun 1.0+ recommended.
Quick start
import backup from 'dis-backups';
const data = await backup.create(guild, { jsonBeautify: true });
console.log(data.id);
await backup.load(data.id, guild);API
create(guild, options)- creates a backup, returns the dataload(backupID | backupData, guild, options)- restores a backup on a guildfetch(backupID)- returns{ id, size, data }remove(backupID)- deletes the filelist()- returns array of backup IDssetStorageFolder(path)- changes where backups are savedon(event, listener)- subscribe toprogressandrestoreCompleteevents
Create options
backupID- custom ID, validated against^[A-Za-z0-9_-]{1,128}$maxMessagesPerChannel- default 10, use Infinity for full historyfetchAllMessages- paginate full channel historyjsonSave- default truejsonBeautify- default true (ignored with gzip)compression-"gzip"or"none"(default)doNotBackup- array of:roles,channels,emojis,bans,members,threads,forums,scheduledEvents,autoModRules,webhooksbackupMembers- default falsesaveImages-""(URL only) or"base64"
Load options
clearGuildBeforeRestore- default truesafetyBackup- default true, snapshots guild before clearingpreserveExisting- default false, non-destructive mode (adds-restoredsuffix)maxMessagesPerChannel- default 10allowedMentions- forwarded to webhook sends
Events
backup.on('progress', (data) => {
// { phase, current, total, percent, guildId }
});
backup.on('restoreComplete', (stats) => {
// { duration, itemsCreated, errors, guildId, backupId }
});Zero cost when no listener attached.
What gets backed up
- Server name, icon, banner, splash
- Verification level, explicit content filter, message notifications
- Widget config
- AFK channel + timeout
- Roles (permissions, color, hoist, mentionable)
- Channels (text, voice, announcement) with permissions
- Messages via webhooks (username, avatar, content, embeds, attachments)
- Threads (active standalone + channel threads)
- Forum channels and posts
- Scheduled events
- AutoMod rules
- Webhooks (name, avatar, channel - no token, Discord doesn't expose it)
- Emojis (optional base64)
- Bans with reasons
Can't restore: audit logs, invites, vanity URL, webhook tokens.
Permissions
create() needs: ManageGuild, ManageChannels, ManageRoles, ManageEmojisAndStickers. load() needs the same + ManageWebhooks, plus BanMembers if backup has bans.
Throws MissingPermissionError with a clear message if missing. Guild owners bypass.
Example bot
const { Client, GatewayIntentBits, Events } = require('discord.js');
const backup = require('dis-backups');
const client = new Client({
intents: [
GatewayIntentBits.Guilds,
GatewayIntentBits.GuildMembers,
GatewayIntentBits.GuildBans,
GatewayIntentBits.GuildEmojisAndStickers,
GatewayIntentBits.GuildMessages,
GatewayIntentBits.MessageContent
]
});
const prefix = 'b!';
client.once(Events.ClientReady, (c) => console.log(`Ready as ${c.user.tag}`));
client.on(Events.MessageCreate, async (message) => {
if (!message.content.startsWith(prefix) || message.author.bot || !message.guild) return;
const command = message.content.toLowerCase().slice(prefix.length).split(' ')[0];
const args = message.content.split(' ').slice(1);
if (command === 'create') {
if (!message.member.permissions.has('Administrator')) {
return message.reply(':x: Admin only.');
}
const data = await backup.create(message.guild, { jsonBeautify: true });
await message.author.send(`Backup created. Load with: \`${prefix}load ${data.id}\``);
return message.reply(`:white_check_mark: Backup **${data.id}** created.`);
}
if (command === 'load') {
if (!message.member.permissions.has('Administrator')) {
return message.reply(':x: Admin only.');
}
const backupID = args[0];
if (!backupID) return message.reply(':x: Need a backup ID.');
try {
await backup.fetch(backupID);
} catch {
return message.reply(`:x: No backup found for \`${backupID}\`.`);
}
await message.reply(':warning: This replaces everything. Type `-confirm` within 20s.');
try {
await message.channel.awaitMessages({
filter: (m) => m.author.id === message.author.id && m.content === '-confirm',
max: 1,
time: 20_000,
errors: ['time']
});
} catch {
return message.reply(':x: Timed out or cancelled.');
}
try {
await backup.load(backupID, message.guild);
await backup.remove(backupID);
return message.author.send(':white_check_mark: Backup loaded.');
} catch (err) {
console.error(err);
return message.author.send(':x: Load failed. Check bot permissions.');
}
}
});
client.login(process.env.DISCORD_TOKEN);Storage
Backups go to ./backups/ by default. Change with backup.setStorageFolder('/path').
For multi-process, point at the same network FS or set jsonSave: false and persist backupData yourself.
Compression
await backup.create(guild, { compression: 'gzip' });
// writes <id>.json.gz, load() detects and decompresses transparentlyUses native zlib on Node, Bun.gzipSync on Bun (2-4x faster on small files).
Testing
bun install
bun test
bun run build
bun run bench57 tests, ~4.5s. Mocks discord.js, no real token needed.
Benchmarks
Run bun run bench to compare Bun vs Node on your machine. Typical results on Bun 1.3.14:
- gzip decompress: Bun 2.2x faster than zlib
- gzip write (small files): Bun 4.25x faster than fs.writeFile
- gzip compress: parity (Bun delegates to zlib C impl)
- large JSON read/write: Node slightly faster (lower per-call overhead)
