What ticket transcripts do and why you need them
A ticket transcript in Discord.js V14 is a saved record of all messages in a support ticket channel — usually formatted as HTML or plain text that you can read outside Discord. When a user opens a support ticket, talks to your staff, and the ticket closes, a transcript captures that entire conversation so you have a permanent log.
Transcripts matter because Discord itself doesn't keep deleted channels forever, and staff members leave or forget details. A transcript is proof of what was said, what was promised, and what was resolved. It also lets you review how your support team handled requests, spot patterns in common problems, and settle disputes if a user claims something happened that didn't.
Discord.js V14 is the current version of the JavaScript library that lets you build bots. The code structure changed significantly from V13, so older transcript tutorials won't work — you need the V14 syntax.
Key Takeaways
- Transcripts are HTML or text files that record every message in a ticket channel before it closes, and you store them on your server or in a database.
- The discord-html-transcripts package is the most common tool for V14 and generates formatted HTML files that look like Discord messages.
- You generate a transcript when a ticket closes, usually by calling a function that reads the channel history and writes the file.
- You need to decide where to store transcripts — a folder on your server, a database, or a cloud service — before you write the code.
- Testing your transcript code with a real ticket channel is essential because the message fetch and file write steps often fail silently if permissions are wrong.
Setting up discord-html-transcripts for V14
The discord-html-transcripts package is the standard choice for V14 because it handles the messy work of fetching messages and formatting them as HTML. Install it with npm in your bot's directory:
npm install discord-html-transcripts
At the top of the file where you handle ticket closure, import the package:
const { createTranscript } = require('discord-html-transcripts');
If you're using ES modules instead of CommonJS, use import { createTranscript } from 'discord-html-transcripts'; instead. Check your package.json to see which one your project uses — if it has "type": "module", you need the import syntax.
Writing the transcript when a ticket closes
When a user or staff member closes a ticket, you run code that calls createTranscript() on the channel. Here's the basic structure for a slash command that closes a ticket:
client.on('interactionCreate', async (interaction) => { if (!interaction.isButton()) return; if (interaction.customId === 'close_ticket') { const channel = interaction.channel; const transcript = await createTranscript(channel, { limit: -1, returnType: 'attachment', filename: `transcript-${channel.id}.html` }); await interaction.reply({ files: [transcript] }); await channel.delete(); } });
Breaking this down: limit: -1 fetches every message in the channel, not just the last 100. returnType: 'attachment' returns a Discord attachment object you can send in a message. filename sets what the HTML file is called — using the channel ID ensures each transcript has a unique name.
The code sends the transcript to the channel before deleting it, so staff can read it. If you want to save it somewhere else instead — like a database or folder — change returnType to 'buffer' and write the buffer to disk using Node's fs module.
Saving transcripts to a folder on your server
If you want to keep transcripts as files instead of sending them to Discord, use Node's built-in fs module. At the top of your file, add:
const fs = require('fs');
Then modify the close ticket code to write the file:
const transcript = await createTranscript(channel, { limit: -1, returnType: 'buffer', filename: `transcript-${channel.id}.html` }); fs.writeFileSync(`./transcripts/transcript-${channel.id}.html`, transcript);
This creates a transcripts folder in your bot's directory and saves each transcript as an HTML file. Make sure the folder exists before you run this — create it manually or add if (!fs.existsSync('./transcripts')) fs.mkdirSync('./transcripts'); at the start of your bot.
The downside of file storage is that if your bot runs on a hosting service that resets the file system (like some free tiers), you lose the transcripts. For production bots, storing transcripts in a database like MongoDB is safer.
Storing transcripts in a database
If you use MongoDB or another database, you can save the transcript buffer as a base64 string. First, convert the buffer:
const transcript = await createTranscript(channel, { limit: -1, returnType: 'buffer' }); const transcriptBase64 = transcript.toString('base64');
Then save it to your database along with metadata:
await db.collection('transcripts').insertOne({ ticketId: channel.id, userId: channel.topic, closedAt: new Date(), transcript: transcriptBase64 });
To retrieve it later, fetch the base64 string and convert it back to a buffer:
const record = await db.collection('transcripts').findOne({ ticketId: channelId }); const transcript = Buffer.from(record.transcript, 'base64');
Database storage is more reliable than files because the data persists even if your bot restarts or moves servers. The trade-off is that you need to set up and maintain a database connection.
Handling permissions and common errors
The most common reason transcripts fail silently is that your bot doesn't have permission to read message history in the ticket channel. Make sure your bot role has the Read Message History permission on all ticket channels.
If createTranscript() returns an empty file or errors out, check these things in order:
- Does your bot have Read Messages/View Channels and Read Message History on the ticket channel?
- Is the channel actually a text channel, not a voice or forum channel?
- Are there any messages in the channel at all? An empty channel produces an empty transcript.
- Is your bot token valid and the bot still in the server?
If you're getting a "Cannot read property 'messages' of undefined" error, the channel object is null — this usually means the channel was already deleted before the code ran. Add a check: if (!channel) return interaction.reply('Channel not found');
Customizing transcript appearance
The discord-html-transcripts package accepts options to change how the transcript looks. You can add a custom footer, change the timezone, or include bot tags:
const transcript = await createTranscript(channel, { limit: -1, returnType: 'attachment', filename: `transcript-${channel.id}.html`, footerText: 'Support Ticket Closed', tz: 'America/New_York' });
The tz option sets the timezone for message timestamps — use standard timezone strings like America/Los_Angeles, Europe/London, or Asia/Tokyo. Without it, timestamps show in UTC.
For more advanced customization, you can pass a theme option or write your own HTML template, but the default styling is clean and readable for most use cases.
Frequently Asked Questions
Can I generate a transcript without deleting the channel?
Yes. The createTranscript() function only reads messages — it doesn't delete anything. You can call it, save the transcript, and leave the channel open. Many bots generate transcripts when a ticket closes but keep the channel archived for a few days before deletion.
What happens if a ticket has thousands of messages?
The transcript will be large but it will work. limit: -1 fetches all messages, which can take a few seconds for very long channels. If you want to speed it up, set limit to a number like 500 to fetch only the last 500 messages instead.
Can I send transcripts to a specific channel instead of the ticket channel?
Yes. Instead of await interaction.reply({ files: [transcript] }), send it to any channel you have access to: const logChannel = client.channels.cache.get('CHANNEL_ID'); await logChannel.send({ files: [transcript] });
Do transcripts include deleted messages?
No. Once a message is deleted from Discord, your bot can't read it. The transcript only includes messages that exist when you generate it. This is a Discord API limitation, not a discord-html-transcripts limitation.
What format should I use — HTML, JSON, or plain text?
HTML is the most readable and looks like Discord. Use returnType: 'attachment' for HTML. If you need raw data to process later, use returnType: 'json' instead, which returns an object you can parse and store however you want.