discord.js-html-transcript

The most advanced library for generating beautiful, pixel-perfect Discord chat HTML transcripts with full modern UI support.

npm version downloads discord
🔀 Forwarded Messages
🎙️ Voice Messages
📊 Polls
🏷️ Server Tags
🔘 Buttons & Menus
🔗 Invite Previews
🖼️ Image Lightbox
🔍 Message Search
🧵 Thread Previews
🎨 ANSI Blocks
📱 Responsive
💾 Asset Saving
💡
Why this library?

Unlike other transcript packages that dump text into basic HTML, this library perfectly renders the native Discord UI - every button, embed, poll, voice message, and thread is pixel-perfect. The output is a standalone HTML file that works offline.


Installation

Install with your preferred package manager. Requires Node.js 16+ and discord.js v14 or v15.

npm install discord.js-html-transcript
pnpm add discord.js-html-transcript
yarn add discord.js-html-transcript

Quick Start

The simplest way to generate a transcript and send it in a channel.

bot.js
const discordTranscripts = require('discord.js-html-transcript');

client.on('interactionCreate', async (interaction) => {
  if (!interaction.isChatInputCommand() || interaction.commandName !== 'transcript') return;

  const transcript = await discordTranscripts.createTranscript(interaction.channel, {
    returnType: 'attachment',
    filename: `transcript-${interaction.channel.id}.html`,
    limit: -1,
    poweredBy: true,
  });

  await interaction.reply({
    content: 'Here is the transcript!',
    files: [transcript],
  });
});

createTranscript(channel, options?)

Async

Fetches all messages from a Discord channel and returns the rendered HTML transcript. This is the primary function for most use cases.

Parameters

ParameterTypeDescription
channelTextBasedChannelThe Discord channel to generate the transcript from.
optionsCreateTranscriptOptionsOptional configuration object. See Options below.

Returns

Depending on returnType: an AttachmentBuilder, Buffer, or string.

Example

transcript.js
const { createTranscript, ExportReturnType } = require('discord.js-html-transcript');

// Generate as an AttachmentBuilder (default)
const attachment = await createTranscript(channel, {
  returnType: ExportReturnType.Attachment,
  filename: 'transcript.html',
  limit: -1,
});

// Generate as a raw HTML string
const html = await createTranscript(channel, {
  returnType: ExportReturnType.String,
});

// Generate as a Buffer
const buffer = await createTranscript(channel, {
  returnType: ExportReturnType.Buffer,
});

generateFromMessages(messages, channel, options?)

Async

Generates a transcript from a pre-fetched array or Collection of messages. Use this when you want full control over which messages to include.

⚠️
Message Ordering

Messages should be passed in chronological order (oldest first). The library does not sort them automatically.

Parameters

ParameterTypeDescription
messagesMessage[] | CollectionArray or Collection of messages to render.
channelChannelThe channel these messages belong to (used for header info).
optionsGenerateFromMessagesOptionsOptional configuration object.

Example

partial.js
const messages = await channel.messages.fetch({ limit: 50 });

const transcript = await discordTranscripts.generateFromMessages(
  messages,
  channel,
  {
    returnType: 'attachment',
    filename: 'partial-transcript.html',
  }
);

await channel.send({ files: [transcript] });

Options

Both createTranscript and generateFromMessages accept an options object. All fields are optional.

OptionTypeDefaultDescription
returnType'attachment' | 'buffer' | 'string''attachment'The output format of the generated transcript.
filenamestring'transcript-{id}.html'The name of the file if returning an attachment.
limitnumber-1Max messages to fetch. -1 fetches all messages. (createTranscript only)
filter(msg) => booleanundefinedFilter function evaluated per message. (createTranscript only)
saveImagesbooleanfalseDownloads images and encodes them as base64 data URIs.
saveAssetsbooleanfalseDownloads all supported assets as base64 data URIs.
assetsobject{}Fine-grained asset preservation. See Asset Preservation.
featuresobject{}Toggle UI features. See Feature Toggles.
callbacksobject{}Custom resolver callbacks. See Callbacks.
poweredBybooleantrueWhether to show the "Powered by" footer.
footerTextstring'Exported {number} message{s}.'Custom footer text. {number} and {s} are placeholders.
favicon'guild' | string'guild'Set to 'guild' to use the server icon, or pass a custom URL.
hydratebooleanfalseWhether to provide React hydration props in the output HTML.

Feature Toggles

Control which interactive features are injected into the transcript via options.features. All features default to enabled.

FeatureDefaultDescription
searchtrueBuilt-in message search bar with keyboard shortcuts (Ctrl+F).
imagePreviewtrueClick any image to open a fullscreen lightbox overlay.
spoilerRevealtrueClick on spoiler tags to reveal hidden content.
messageLinkstrueClick a reply reference to smooth-scroll to the original message.
profileBadgestrueRender APP badges, server tags, and role icons next to usernames.
embedTweakstrueClient-side CSS fixes for embed borders and styling.

Example

features.js
const transcript = await createTranscript(channel, {
  features: {
    search: true,
    imagePreview: true,
    spoilerReveal: true,
    messageLinks: true,
    profileBadges: true,
    embedTweaks: false,  // disable embed style fixes
  },
});

Asset Preservation

By default, transcripts reference remote Discord CDN URLs for images and avatars. These URLs expire over time. Use options.assets to embed specific asset types directly into the HTML as base64 data URIs for permanent offline access.

AssetDefaultDescription
attachmentsfalseUser-uploaded images, videos, audio, and file attachments.
embedsfalseEmbed images, thumbnails, video posters, and author/footer icons.
componentsfalseMedia gallery items, thumbnails, and file components.
avatarsfalseUser profile pictures used throughout the transcript.
emojisfalseCustom Discord emojis and standard Twemoji images.
guildIconsfalseServer icon shown in the transcript header/favicon.
inviteIconsfalseServer icons inside invite link preview cards.
roleIconsfalseHighest-role icon images displayed next to usernames.
serverTagBadgesfalseBadge images shown inside server tag labels.

Example - Full Offline Transcript

offline.js
const { createTranscript, TranscriptAssetDownloader } = require('discord.js-html-transcript');

const transcript = await createTranscript(channel, {
  assets: {
    attachments: true,
    embeds: true,
    avatars: true,
    emojis: true,
    guildIcons: true,
    inviteIcons: true,
    roleIcons: true,
    serverTagBadges: true,
  },
  callbacks: {
    resolveAssetSrc: new TranscriptAssetDownloader()
      .withMaxSize(10240)
      .build(),
  },
});

Image Compression

When saving assets, file sizes can grow significantly. Use the TranscriptImageDownloader class with sharp to automatically resize and compress images to WebP before embedding.

📦
Dependency Required

You must install sharp separately: npm install sharp

TranscriptImageDownloader

MethodDescription
.withMaxSize(kb)Skip images larger than this size in kilobytes.
.withCompression(quality, webp?)Set compression quality (1-100) and optionally force WebP conversion.
.build()Returns the resolveImageSrc callback function.

Example

compressed.js
const { TranscriptImageDownloader } = require('discord.js-html-transcript');

const transcript = await createTranscript(channel, {
  callbacks: {
    resolveImageSrc: new TranscriptImageDownloader()
      .withMaxSize(5120)           // Skip images > 5MB
      .withCompression(40, true)  // 40% quality, force WebP
      .build(),
  },
});

Callbacks

Override how channels, users, roles, invites, and assets are resolved via options.callbacks.

CallbackSignatureDescription
resolveChannel(id: string) => Channel | nullResolve a channel mention by ID.
resolveUser(id: string) => User | nullResolve a user mention by ID.
resolveRole(id: string) => Role | nullResolve a role mention by ID.
resolveInvite(code: string) => InviteData | nullResolve a Discord invite code into rich preview data.
resolveImageSrc(attachment) => string | nullCustom image resolver (e.g. TranscriptImageDownloader).
resolveAssetSrc(asset) => string | nullCustom asset resolver (e.g. TranscriptAssetDownloader).

Example - Custom Invite Resolver

callbacks.js
const transcript = await createTranscript(channel, {
  callbacks: {
    resolveInvite: async (code) => {
      const invite = await client.fetchInvite(code).catch(() => null);
      if (!invite?.guild) return null;

      return {
        name: invite.guild.name,
        icon: invite.guild.iconURL({ size: 128 }),
        online: invite.presenceCount ?? 0,
        members: invite.memberCount ?? 0,
      };
    },
  },
});

UI Previews

Click any image to zoom in. Every element is rendered to match the native Discord UI.

Image AttachmentImage Attachment
Multi ImageMulti-Image Gallery
FileFile Attachment
ButtonsButtons & Select Menu
Slash & VoiceSlash Command & Voice Message
InviteInvite Link Preview
EmbedRich Embed
Embed FieldsEmbed with Fields
Embed ImageEmbed with Image
ForwardedForwarded Message
Forwarded ReactionsForwarded + Reactions
PollPoll / Voting
ThreadThread Preview
MentionsMentions
LinksLinks
MarkdownMarkdown Formatting
Code BlocksCode Blocks
HeadingsHeadings
QuotesBlock Quotes
ListsLists
ANSIANSI Code Blocks

Live Demo

A real transcript rendered live inside the browser.

demo.html

Demo Not Found

Place your generated demo.html transcript file in the /site directory to see it live here.