Skip to content

Repository files navigation

@fmfl-devteam/discord-html-transcripts

Create secure, responsive, standalone HTML transcripts from Discord channels or already loaded discord.js messages. The package targets discord.js 14.27.x and renders classic interactive message components as well as Components V2 display components.

The generated document contains its CSS and requires no JavaScript, framework, CDN stylesheet, or web font. It supports light, dark, automatic, mobile, and print layouts.

Repository note: the repository, homepage, and issue URLs in package.json use the visible example.invalid placeholder because no canonical public repository URL was available when this package was created. Replace those metadata values before the first publication.

Requirements

  • Node.js 20 or newer
  • discord.js ^14.27.0
  • ViewChannel and ReadMessageHistory permissions when fetching a channel

Installation

npm install @fmfl-devteam/discord-html-transcripts discord.js
pnpm add @fmfl-devteam/discord-html-transcripts discord.js
yarn add @fmfl-devteam/discord-html-transcripts discord.js

Quick Start

import { createTranscript } from "@fmfl-devteam/discord-html-transcripts";

const transcript = await createTranscript(channel, {
  filename: `transcript-${channel.id}.html`,
  theme: "dark",
  includeComponents: true,
  includeAttachments: true,
});

await targetChannel.send({
  files: [transcript.attachment],
});

createTranscript() fetches up to 100 messages per request, follows the oldest message ID with before, removes duplicate IDs, and produces a stable oldest-to-newest transcript. The default maximum is 10,000 messages. Discord API and permission failures are wrapped in a TranscriptFetchError with channel and permission context.

Loaded Messages

import { generateFromMessages } from "@fmfl-devteam/discord-html-transcripts";

const transcript = await generateFromMessages(messages, {
  title: "Ticket Transcript",
  theme: "auto",
});

The input may be a Collection<string, Message> or a readonly Message[]. Input order does not matter; messages are sorted chronologically. A limit keeps the newest N messages.

Save to Disk

import { writeFile } from "node:fs/promises";
import { createTranscript } from "@fmfl-devteam/discord-html-transcripts";

const transcript = await createTranscript(channel, {
  title: "Audit Transcript",
  poweredBy: false,
});

await writeFile(transcript.filename, transcript.html, "utf8");

API

interface TranscriptOptions {
  limit?: number;
  filename?: string;
  title?: string;
  description?: string;
  includeSystemMessages?: boolean;
  includeAttachments?: boolean;
  includeComponents?: boolean;
  includeEmbeds?: boolean;
  includeReactions?: boolean;
  includeAvatars?: boolean;
  includeUserIds?: boolean;
  includeChannelId?: boolean;
  poweredBy?: boolean;
  saveImages?: boolean;
  maxInlineMediaBytes?: number;
  maxSingleMediaBytes?: number;
  theme?: "light" | "dark" | "auto";
  timezone?: string;
  locale?: string;
  generatedAt?: Date;
}
Option Default Description
limit 10000 Newest messages to fetch or retain; integer from 1 through 10,000.
filename transcript.html Attachment filename. Unsafe filesystem characters are replaced and .html is appended.
title Channel-based title Transcript heading and document title.
description none Plain-text description in the document header.
includeSystemMessages true Include Discord system event messages.
includeAttachments true Render image, video, audio, text, and generic file attachments.
includeComponents true Render classic components and Components V2.
includeEmbeds true Render rich embeds.
includeReactions true Render reaction emoji and counts.
includeAvatars true Render author avatars; initials are used otherwise.
includeUserIds false Add author IDs as data-author-id metadata.
includeChannelId false Include the channel ID in header metadata.
poweredBy true Include the package attribution footer.
saveImages false Inline supported Discord-CDN raster images as data URLs.
maxInlineMediaBytes 26214400 Total inlined-image budget, at most 100 MiB.
maxSingleMediaBytes 8388608 Per-image budget, at most 100 MiB.
theme auto Light, dark, or operating-system color scheme.
timezone UTC IANA timezone used for message and embed timestamps.
locale en-US Intl locale used for dates and numbers.
generatedAt current time Explicit archive creation time, useful for deterministic exports.

The result contains the complete html, the sanitized filename, and a ready-to-send discord.js AttachmentBuilder.

Components V2

Message components are not assumed to be Action Rows. discord.js structures are converted through their toJSON() representation and normalized into a recursive discriminated union.

Supported message component types from discord.js 14.27.x and Discord API v10:

  • Action Rows with Primary, Secondary, Success, Danger, Link, and Premium buttons
  • String, User, Role, Mentionable, and Channel Select menus
  • Containers with accent color, spoiler state, and ordered nested content
  • Text Displays with the same safe Discord Markdown renderer as message content
  • Sections containing Text Displays with a Button or Thumbnail accessory
  • Separators with Small or Large spacing and visible or invisible dividers
  • responsive Media Galleries with item descriptions and per-item spoilers
  • File display components, including attachment://filename resolution
  • Thumbnails with descriptions, spoilers, and unavailable-media fallbacks

Modal-only Text Inputs, Labels, File Uploads, Radio Groups, and Checkboxes are not treated as message components. Unknown future message component types render a small static fallback and never abort the transcript.

Content Support

Messages include author and display names, bot/webhook badges, optional IDs, avatars, timestamps, edits, pins, TTS, system events, threads, replies, unavailable references, embeds, attachments, and reactions. Replies are resolved when the referenced message is present in the provided/fetched set.

The Markdown renderer supports bold, italic, underline, strikethrough, spoilers, inline and fenced code, headings, lists, blockquotes, masked and plain links, Discord timestamps, user/role/channel mentions, Unicode emoji, and custom Discord emoji.

Security

All Discord-controlled strings are HTML-escaped before markup is produced. The renderer never passes message HTML through, does not emit scripts, and adds a restrictive Content Security Policy. URLs are parsed centrally and allow only HTTPS for embedded media; ordinary links additionally allow HTTP. javascript:, arbitrary data:, file:, malformed attachment references, inline styles from message data, and SVG/data payloads supplied by messages are rejected. Generated links use noopener, noreferrer, and nofollow.

saveImages is deliberately constrained:

  • only HTTPS hosts in Discord's discordapp.com and discordapp.net media domains are fetched;
  • redirects are rejected;
  • only PNG, JPEG, GIF, WebP, and AVIF response types are accepted;
  • response streams stop at the configured per-item and total byte budgets;
  • failures leave the original secure URL in place and do not fail transcript generation.

Inlining makes the HTML larger. The generated file can still reference external HTTPS media when inlining is disabled, a file exceeds its budget, or a CDN request fails.

Known Limitations

  • Discord CDN links can expire and externally hosted media can disappear.
  • Only referenced messages included in the input set can be expanded; missing messages use a safe unavailable-reference marker and no extra API request is made.
  • Discord Markdown edge cases can evolve independently of this package. The renderer intentionally favors predictable, escaped output over executing arbitrary HTML or matching every client quirk.
  • Media fallbacks are structural. A standalone HTML file cannot detect a later image load failure without JavaScript.
  • saveImages does not inline videos, audio, SVG, arbitrary hosts, or generic files.

Development

npm install
npm run lint
npm run typecheck
npm test
npm run build
npm pack --dry-run

npm test builds first so the test suite also validates the scoped package-root import. See CONTRIBUTING.md for fixture and pull-request expectations. A complete discord.js bot example is in examples/basic.ts. Biome performs formatting, import organization, and lint checks through npm run format and npm run lint.

Publishing

This package is configured as the public scoped package @fmfl-devteam/discord-html-transcripts. Tagged releases are published by .github/workflows/release.yml. A tag must be exactly v followed by the version in package.json. The workflow runs the complete prepublish check, publishes the public npm package, and creates a GitHub Release with generated release notes.

Configure npm Trusted Publishing for this package with these values:

  • Provider: GitHub Actions
  • Organization: fmfl-devteam
  • Repository: discord-html-transcripts
  • Workflow filename: release.yml
  • Allowed action: npm publish

Trusted Publishing uses short-lived OIDC credentials and requires no permanent npm token. The workflow uses a GitHub-hosted Node.js 24 runner and has the required id-token: write permission. If the package has not been published before and npm does not yet allow configuring its Trusted Publisher, add a granular publishing token temporarily as the NPM_TOKEN repository secret for the initial release. Configure Trusted Publishing afterward and delete that secret.

Create a release by updating the version and pushing its generated tag:

npm version patch
git push origin main --follow-tags

Use minor or major instead of patch when appropriate. prepublishOnly runs Biome, strict typechecking, tests, and the dual ESM/CommonJS build. Workflow retries are safe: an existing npm version and an existing GitHub Release are detected and skipped. Never commit npm tokens or put them in workflow files.

Contributing

Contributions are welcome under the process in CONTRIBUTING.md. Changes to Discord structures should use payloads from Discord API v10 and types/classes exported by the supported discord.js version, with security and snapshot coverage for renderer changes.

License

MIT © Fmfl-Devteam. See LICENSE.

About

Generate secure, standalone HTML transcripts from discord.js channels and messages, including Components V2.

Topics

Resources

Contributing

Stars

1 star

Watchers

1 watching

Forks

Releases

Sponsor this project

Contributors

Languages