Skip to main content

Advanced Features

JSON Export

Sticks the raw TranscriptData as JSON inside the HTML file so you can extract it later programmatically.

Option: includeJson: true

const html = generateTranscriptHtml(data, {
includeJson: true,
});

The JSON is embedded as a <script> tag with type application/json:

<script type="application/json" id="discord-transcript-json">
{"channel":{"id":"123","name":"general",...},"messages":[...]}
</script>

Extracting the data

In a browser:

const el = document.getElementById("discord-transcript-json");
const data = JSON.parse(el.textContent);
console.log(data.messages.length);

With Node.js (parsing the HTML file):

import { readFileSync } from "fs";

const html = readFileSync("transcript.html", "utf-8");
const match = html.match(
/<script type="application\/json" id="discord-transcript-json">([\s\S]*?)<\/script>/
);
if (match) {
const data = JSON.parse(match[1]);
console.log(`${data.messages.length} messages`);
}

Pagination

Breaks the transcript into pages with Previous/Next buttons at the bottom.

Option: messagesPerPage: <number>

const html = generateTranscriptHtml(data, {
messagesPerPage: 50,
});
  • Splits messages into chunks of the given size
  • Shows Previous / Page X of Y / Next at the bottom
  • Only the current page is visible
  • Works alongside search and other features
tip

For very large transcripts (1000+ messages), pagination significantly improves browser performance since only one page of messages is rendered at a time.

Choosing a page size

MessagesRecommended messagesPerPage
< 200No pagination needed
200–500100
500–200050
2000+25–50

Custom Component Renderers

Let you swap out how specific component types get rendered. Handy if you want custom branding or need to handle a component differently.

Option: customRenderers: Record<number, (component: DiscordComponent) => string>

import { generateTranscriptHtml } from "@getoeteter/discord-transcripts";
import type { DiscordComponent } from "@getoeteter/discord-transcripts";

const html = generateTranscriptHtml(data, {
customRenderers: {
// Override button rendering (type 2)
2: (component: DiscordComponent) => {
const label = component.label || "Button";
return `<div class="my-custom-button">${label}</div>`;
},

// Override container rendering (type 17)
17: (component: DiscordComponent) => {
return `<div class="my-container">${component.content || ""}</div>`;
},
},
});

How it works

  1. Before rendering a component, we check if you've provided a custom renderer for that type
  2. If you have, we call it with the full DiscordComponent object
  3. Your function needs to return an HTML string
  4. Child components are not automatically rendered - your custom renderer handles the full output

Component type reference

TypeNameUse case for override
1ActionRowCustom layout
2ButtonCustom styling or click handlers
3StringSelectCustom dropdown rendering
9SectionCustom section layout
10TextDisplayCustom text rendering
12MediaGalleryCustom gallery layout
17ContainerCustom container styling
caution

Custom renderers receive raw component data. You are responsible for sanitizing any user content in your output. Use HTML escaping for any text you render.