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

@fmfl-devteam/discord-html-transcripts

v0.1.0

Published

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

Readme

@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.