npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

discord-html-transcript

v1.2.2

Published

Pixel-accurate, self-contained HTML transcripts for Discord channels. XSS-safe by construction, CSP-locked, split into parts when a file would exceed the guild's upload limit.

Readme

discord-html-transcript

Turn any Discord channel into a beautiful HTML transcript that looks exactly like Discord — with one function call.

اقرأ هذا الملف بالعربية →

[!NOTE] discord-html-transcript is also published as discord-transcriber. Same package, same author (ieissa on npm, eissa99 on GitHub), same code, same versions — kept in lockstep by an automated sync. Install whichever name you prefer; neither is a fork of the other.

import { createTranscript } from 'discord-html-transcript';

const transcript = await createTranscript(channel);

// One file per message: Discord's upload limit applies to the whole request.
for (const file of transcript.files) {
  await logChannel.send({ files: [file] });
}

All examples in this document are plain JavaScript. Using CommonJS? Same one line:

const { createTranscript } = require('discord-html-transcript');

TypeScript users get complete type definitions automatically.

What it renders

Everything, the way Discord shows it:

  • Messages with complete Discord markdown — headers, subtext, nested lists, quotes, code, spoilers (click to reveal) — plus custom emoji and mentions resolved to real names
  • Replies with working jump links, and message grouping exactly like the client
  • Forwarded messages, with a pressable origin row that jumps to the original in-file or opens it on Discord
  • Attachments — images inline, audio and video with players, other files as download cards
  • Rich embeds, buttons and select menus
  • Components V2 layouts: containers, sections, text displays, media galleries, files, separators
  • Slash-command rows ("Eissa used /close"), reactions, stickers
  • Thread cards with the thread's name, message count, and a preview of its latest message
  • System events (pins, joins, boosts), edited and pinned markers, day dividers

All in Discord's own colours, spacing and typography.

Why this package

  • Looks exactly like Discord. The styling is sampled from the Discord client itself, down to the smallest details.
  • Safe by design. Every piece of message content is escaped before it reaches the HTML, and each file carries a strict Content-Security-Policy: no script can run except the file's own, and images and media load only from Discord's CDN. Hostile message content cannot execute or call out.
  • One self-contained file. No hosting, no external service, no CDN scripts. The file opens offline and prints cleanly.
  • Splits big transcripts automatically. A transcript that would exceed the server's upload limit becomes several complete files, each sized to the server's boost tier.
  • Light and typed. discord.js is the only dependency (peer). Ships ESM and CommonJS with full TypeScript definitions.

Install

npm install discord-html-transcript

Requires Node.js 20+ and discord.js v14.19+ (peer dependency) — the first release with Components V2, which transcripts render. On an older discord.js, npm install discord.js@latest brings you up.

⚠️ Message Content intent — required. Without the privileged Message Content intent, Discord hides content, embeds, attachments and components from message history, so every message not written by your bot arrives empty — and the transcript shows blank bubbles with no error anywhere. Enable the intent on the Bot page of the Developer Portal and declare it in your client:

new Client({ intents: [GatewayIntentBits.Guilds, GatewayIntentBits.MessageContent] });

Usage

Basic usage

import { createTranscript } from 'discord-html-transcript';

const transcript = await createTranscript(channel, {
  limit: 2000,                  // newest 2000 messages; default: all
  filename: 'ticket-1042',      // default: transcript-<channel name>
});

transcript.files;        // AttachmentBuilder[] — ready for channel.send({ files })
transcript.parts;        // { filename, content: Buffer, partNumber, ... }[]
transcript.messageCount; // how many messages were collected
transcript.truncated;    // true when the channel held more than `limit`
transcript.byteSize;     // total size of all parts

Works with any text channel in a server: text and announcement channels, threads, and voice/stage chats.

Send one file per message — Discord's upload limit applies to the whole request:

const [first, ...rest] = transcript.files;
await logChannel.send({ content: 'Transcript', files: [first] });
for (const file of rest) await logChannel.send({ files: [file] });

Or save the files to disk:

import { writeFile } from 'node:fs/promises';
for (const part of transcript.parts) {
  await writeFile(part.filename, part.content);
}

Your branding

The conversation always keeps Discord's look. The frame around it — the header, the accent colour, the footer — is yours:

await createTranscript(channel, {
  brand: {
    name: 'CastCord Support',          // bold line above the channel name
    accentColor: '#9146ff',            // frame colour; default Discord blurple
    footerText: 'Generated by CastCord Helper',
    logoSvg: '<svg class="brand-mark" ...>...</svg>', // your logo, header + footer
  },
});

Security note: logoSvg is embedded as-is so the file stays self-contained. Pass only markup you wrote yourself — never user input.

The details panel

Record facts that are not part of the conversation itself — who opened a ticket, why it was closed, what a form answered:

await createTranscript(channel, {
  metadata: [
    { label: 'Category',  value: 'Technical Support', icon: 'tag' },
    { label: 'Opened by', value: '7sO (7so)',         icon: 'person' },
    { label: 'Closed by', value: 'Eissa (eissa)',     icon: 'shield' },
    { label: 'Close reason', value: 'Resolved.', icon: 'note', wide: true },
  ],
  metadataTitle: 'Details', // panel heading — write it in any language
  notices: ['Exported for the moderation review.'],
});

Icons: tag · text · person · shield · people · clock · lock · note · chat. Add wide: true to give a long value its own full row.

All options

| Option | Type | Default | Description | | --- | --- | --- | --- | | limit | number | all messages | Keeps the newest messages; a notice in the file reports the cut. | | filter | (m) => boolean | keep all | e.g. (m) => !m.author.bot. Applied before grouping and reply previews, so the conversation stays coherent. | | maxFileBytes | number \| 'auto' | 'auto' | Max size per file. 'auto' reads the server's boost tier. | | filename | string | transcript-<channel> | Base file name; split parts get -part2of3 suffixes. | | title | string | #channel · guild | The browser-tab title. | | favicon | 'guild' \| 'none' \| url | 'guild' | Browser-tab icon. A custom URL must be on Discord's CDN. | | brand | object | neutral | Header name, logo, accent colour, footer — see above. | | metadata | array | none | The details panel — see above. | | metadataTitle | string | Details | The panel's heading. | | notices | string[] | none | Extra notice banners above the conversation. |

Advanced: collect now, render later

The package is two independent layers. collectMessages turns a channel into plain data; renderTranscript turns that data into HTML files with no Discord connection at all — so you can store the data anywhere and render it whenever you like:

import {
  collectMessages,     // channel -> plain data (messages, mentions, truncated)
  collectFromMessages, // messages you already hold -> the same plain data
  renderTranscript,    // plain data -> HTML files (no Discord needed)
} from 'discord-html-transcript';

const collected = await collectMessages(channel, { limit: 500 });

// ...store `collected` anywhere, then later:
const parts = renderTranscript(
  {
    guildName: channel.guild.name,
    channelName: channel.name,
    messages: collected.messages,
    mentions: collected.mentions,
    truncated: collected.truncated,
    generatedAt: new Date(),
  },
  { maxFileBytes: 8 * 1024 * 1024 },
);

The collector also resolves mentions that Discord leaves out of the message payload (pings suppressed through allowed_mentions — common in bot announcements), so <@id> renders as a name, not a number.

Notes and limitations

  • The Message Content intent is required (see Install above). The package cannot detect its absence — Discord simply returns empty messages.
  • Timestamps are UTC, worded as Discord worded them when the transcript was made (4:30 AM, Yesterday at…); the exact instant is on every tooltip.
  • Media loads from Discord's CDN. Attachment links expire after a while, so images and players in old transcripts may stop loading. The text of the conversation is unaffected — it lives inside the file.
  • DM channels are not supported — the collector reads server context (nicknames, role colours, boost tier).
  • The file contains exactly one script, which only makes reply-jumps scroll smoothly. With JavaScript disabled, everything still works.

Responsible use

A transcript contains your members' messages. Once your bot stores or shares one, that data is your responsibility under Discord's Developer Terms of Service and Developer Policy:

  • Mention transcripts in your bot's privacy policy, so members know their messages may be archived.
  • Share a transcript only with people who could already read the channel — a ticket transcript belongs to the staff and the ticket's participants.
  • Delete stored transcripts when a member or server asks you to.

Demo

To see a full sample transcript, clone the repository and run:

npm install
npm run build
npm run demo    # writes examples/demo.html — open it in your browser

License

MIT

⭐ Found it useful?

A star on GitHub helps a lot — and if you'd like to support my work, I'm on ko-fi.

Made with ❤️ by Eissa


discord-html-transcript is an independent open-source project, not affiliated with or endorsed by Discord Inc.