Package Exports
- better-discord-transcripts
Readme
better-discord-transcripts
A nicely formatted HTML transcript generator for discord.js v14/v15. Drop-in replacement for discord-html-transcripts, with Components V2, thread modals, voice playback, and a built-in search UI. Inspired by community forks such as discord.js-html-transcript.
Images and voice messages are inlined by default so transcripts stay readable offline. With sharp installed, images are compressed to WebP - much smaller HTML files.
Project status
In my opinion, the project is feature-complete for now. I will still update it regularly - bug fixes, discord.js changes, and small improvements as needed.
Requirements
- Node.js 18+
- discord.js
^14.26.4or^15(peer dependency - install it yourself) - sharp (optional peer dependency - recommended for
saveImages: true)
Sharp is not bundled. Install it yourself if you want WebP resize/compression (strongly recommended for ticket bots - without sharp, inlined transcripts can get very large).
Install
Base install (no image compression)
npm install better-discord-transcripts discord.jspackage.json:
{
"dependencies": {
"better-discord-transcripts": "^2.0.0",
"discord.js": "^14.26.4"
}
}With sharp (highly recommended)
npm install better-discord-transcripts discord.js sharppackage.json:
{
"dependencies": {
"better-discord-transcripts": "^2.0.0",
"discord.js": "^14.26.4",
"sharp": "^0.34.3"
}
}Migration from discord-html-transcripts
One line change. Same options, same return types:
- const { createTranscript } = require('discord-html-transcripts');
+ const { createTranscript } = require('better-discord-transcripts');Your existing ticket code still works.
Quick start
Create a full channel transcript in 3 lines of code!:
const { createTranscript } = require('better-discord-transcripts');
const attachment = await createTranscript(channel, { limit: -1 });
await logChannel.send({ files: [attachment] });If you omit limit (or set -1), the full channel history is fetched, including thread messages on posts with hasThread.
Image compression (sharp)
With default options, attachments and embed images are downloaded and embedded as WebP base64 instead of raw PNG/JPEG. That keeps transcript file size down while everything still opens from a single .html file with no external CDN links.
| Setting | Value |
|---|---|
| Library | sharp (optional peer - npm install sharp) |
| Format | WebP |
| Max dimension | 720px (longest side) |
| Quality | ~70% |
| Toggle | saveImages: false skips download and inlining entirely |
| Sharp off | useSharp: false keeps inlining but skips resize/WebP (raw base64) |
Not recommended: turning sharp off (useSharp: false) while keeping saveImages: true. Images stay full resolution and original format (often PNG/JPEG), so a single ticket transcript can easily reach tens or hundreds of MB. Use it only as a fallback when sharp cannot load on your platform.
Sharp is probed when your bot loads the package (require('better-discord-transcripts')). If it is not installed or fails to load, you get a one-time warning at startup (with install instructions). During transcript generation the library falls back to the original image format and still produces HTML - but files may be much larger.
// Default - images compressed with sharp
await createTranscript(channel);
// Skip images - smallest output, but Discord CDN links may expire later
await createTranscript(channel, { saveImages: false });
// Fallback only - not recommended for production (transcripts can get very large)
await createTranscript(channel, { useSharp: false });Options
Same options as discord-html-transcripts, plus saveVoiceMessages:
| Option | Type | Default | Description |
|---|---|---|---|
limit |
number |
all messages | Max messages to fetch. -1 or omit = everything |
filter |
(m) => boolean |
none | Filter messages before render |
returnType |
'attachment', 'buffer', 'string' |
'attachment' |
Or use the ExportReturnType enum |
filename |
string |
transcript-{channelName}.html |
Attachment filename |
saveImages |
boolean |
true |
Download images and inline as WebP base64 via sharp (max 720px, ~70% quality) |
useSharp |
boolean |
true |
Resize/compress inlined images with sharp; false = raw base64 (not recommended - huge HTML files) |
saveVoiceMessages |
boolean |
true |
Download voice messages and inline as base64 so they stay playable after Discord URLs expire |
footerText |
string |
Exported {number} message{s}. |
Footer line before the powered-by credit |
poweredBy |
boolean |
true |
Show "Powered by better-discord-transcripts" |
favicon |
'guild' or string |
'guild' |
Guild icon or a custom URL |
hydrate |
boolean |
false |
Server-side hydration of web components (slower, works offline) |
callbacks |
object | auto | resolveUser, resolveRole, resolveChannel, resolveImageSrc, resolveVoiceSrc |
Disable image or voice inlining
// No images (voice messages still inlined by default)
await createTranscript(channel, { saveImages: false });
// Voice only (images stay as Discord CDN links, but links may break later - remember that!)
await createTranscript(channel, { saveImages: false, saveVoiceMessages: true });
// Neither - smallest file, but CDN links may break later - remember that!
await createTranscript(channel, { saveImages: false, saveVoiceMessages: false });TypeScript
import { createTranscript, ExportReturnType } from 'better-discord-transcripts';
const transcript = await createTranscript(channel, {
returnType: ExportReturnType.String,
});generateFromMessages - when you already have messages
createTranscript fetches messages from Discord for you.
generateFromMessages skips that step. You pass a Message[] or Collection you already loaded (custom fetch, cached ticket log, filtered list, etc.) and get the same HTML output.
Use it when:
- you fetched messages yourself (custom pagination, database, bot cache)
- you want to render only a subset without calling the API again
- you deleted the channel but still have message objects in memory
const { generateFromMessages } = require('better-discord-transcripts');
// messages = Message[] or Collection from channel.messages.fetch(), your DB, etc.
const attachment = await generateFromMessages(messages, channel, {
poweredBy: false,
filename: 'ticket-log.html',
});Same options as createTranscript, except there is no limit or filter (you control the array yourself). saveImages, useSharp, and saveVoiceMessages default to true here as well.
Interactive HTML viewer
Built into every transcript:
- Search - find messages by text or ID, keyboard navigation
- Pinned messages - header panel with jump-to-message
- Member list - participants and message counts
- Thread modal - click a thread bar to read the full thread inline
- Image lightbox - click images to zoom
- Voice playback - play button and waveform (works offline when
saveVoiceMessagesis on) - Spoiler reveal - click to reveal spoilers
Content rendered
- Plain text, markdown, mentions, emojis, timestamps
- Embeds, attachments, stickers, reactions
- Replies, forwards, slash command headers
- Components V2 - Container, Section, TextDisplay, Media Gallery, Thumbnail, File, Separator, buttons
- Polls with vote bars
- Voice messages - waveform from
attachment.waveform, duration, inline<audio> - System messages - pins, thread created, joins, and more
- Server tags, role icons, verified bot badges
- Cross-server reply indicators
Included by default
- Full channel fetch when
limitis omitted - Full thread fetch on messages with
hasThread - Image inlining when
saveImages: true(WebP via sharp when installed) - Images inlined as WebP base64 with sharp (720px max, ~70% quality)
- Voice messages inlined as base64 (
saveVoiceMessages: true) for offline playback
API
| Export | Description |
|---|---|
createTranscript(channel, options?) |
Fetch messages from a channel and render HTML |
generateFromMessages(messages, channel, options?) |
Render HTML from messages you already have |
ExportReturnType |
Attachment, Buffer, or String |
default |
{ createTranscript, generateFromMessages } |
Changelog
2.0.0 (latest)
- Project marked as feature-complete; regular maintenance updates continue
- Collapsible changelog section in README
- Sharp probed at bot startup when the package is loaded
1.0.9
sharpmoved to optional peer dependency (smaller install without image compression)useSharpoption to skip WebP resize/compressionisSharpAvailable()export and automatic fallback when sharp is missing- README install examples with and without sharp
1.0.8
- Slightly darker embed and container backgrounds
- Footer pill:
Exported X messages | Powered by better-discord-transcripts - Unicode separators in UI changed to
-where applicable
1.0.7
- Lighter inline and block code background (
#2b2d31) closer to Discord
1.0.6
- Inline
`code`styling like Discord (inherit font size, wrap-friendly background) - Fenced code blocks (```) match inline code style
1.0.5
- Removed dead emoji registry code
- CDN derock version fix (strip
^from component version) - Select menu options render as
<img>instead of raw URLs
1.0.4
hydrate: trueby default for embed titles with custom emoji- Non-clickable emoji in messages, embeds, buttons, reactions, and polls
- Embed title rendering fixes after hydration
License
Fork and successor of discord-html-transcripts by ItzDerock.