Using Discord.js Ephemeral Replies for Private Slash‑Command Feedback
Learn how to use Discord.js v14’s ephemeral flag to send private, self‑destructing replies to slash commands, keeping channel chat clean and user data hidden from others.
16 Dec 2025, 06:57 UTC

Problem
When a slash command needs to return information that should only be visible to the invoker—such as a user’s profile stats, a temporary token, or a confirmation that contains sensitive data—the default Interaction.reply posts a public message. This clutters the channel and exposes data to everyone who can read it.
Thesis
Discord.js v14 exposes the ephemeral flag through MessageFlags.Ephemeral. By setting this flag when replying to an interaction, the bot sends a self‑destructing view that only the invoking user can see, eliminating the need for extra storage or permission tricks.
Setting up the command
First, create a slash command definition with the Discord.js Builder and register it via the REST API. The bot must have the applications.commands OAuth scope and the Guilds gateway intent.
// commands/ping.js
const { SlashCommandBuilder } = require('@discordjs/builders');
module.exports = {
data: new SlashCommandBuilder()
.setName('privateinfo')
.setDescription('Returns an ephemeral piece of information'),
async execute(interaction) {
// Will be filled in the next section
}
};
Register the command (run once or when you change it):
// deploy-commands.js
const { REST, Routes } = require('discord.js');
const { token, clientId, guildId } = require('./config.json');
const fs = require('node:fs');
const path = require('node:path');
const commands = [];
const commandsPath = path.join(__dirname, 'commands');
const commandFiles = fs.readdirSync(commandsPath).filter(file => file.endsWith('.js'));
for (const file of commandFiles) {
const command = require(`./commands/${file}`);
commands.push(command.data.toJSON());
}
const rest = new REST({ version: '10' }).setToken(token);
(async () => {
try {
console.log(`Started refreshing ${commands.length} application (/) commands.`);
// For guild‑specific testing, use Routes.applicationGuildCommands
await rest.put(Routes.applicationGuildCommands(clientId, guildId), { body: commands });
console.log('Successfully reloaded application (/) commands.');
} catch (error) {
console.error(error);
}
})();
Run node deploy-commands.js from your project root. You need the bot token (token) and the application/client ID (clientId)—both available in the Discord Developer Portal. The guildId limits the command to a test server; remove it for global registration (propagation may take up to an hour).
Handling the interaction
In your main bot file, listen for interactionCreate, filter for CommandInteraction, and call Interaction.reply with the flags option set to MessageFlags.Ephemeral. If you anticipate needing more than three seconds to prepare the response, first call deferReply (with ephemeral: true) and then editReply or followUp.
// index.js
const { Client, GatewayIntentBits, Partials } = require('discord.js');
const { token } = require('./config.json');
const fs = require('node:fs');
const path = require('node:path');
const client = new Client({ intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildMessages] });
client.once('ready', () => {
console.log(`Logged in as ${client.user.tag}`);
});
client.on('interactionCreate', async interaction => {
if (!interaction.isCommand()) return;
const command = interaction.client.commands.get(interaction.commandName);
if (!command) return;
try {
// Example: fetch some private data (could be from a DB, API, etc.)
const privateData = `Your temporary token: ${Math.random().toString(36).substring(2, 8)}`;
await interaction.reply({
content: privateData,
flags: [/* MessageFlags.Ephemeral */ 1 << 6], // 64
});
} catch (error) {
console.error(error);
if (!interaction.replied && !interaction.deferred) {
await interaction.reply({ content: 'There was an error while executing this command!', flags: [1 << 6] });
}
}
});
// Load commands
const commands = [];
const commandFiles = fs.readdirSync('./commands').filter(f => f.endsWith('.js'));
for (const file of commandFiles) {
const command = require(`./commands/${file}`);
client.commands.set(command.data.name, command);
}
client.login(token);
The numeric flag 1 << 6 (64) is the value of MessageFlags.Ephemeral; you can also import it directly: const { MessageFlags } = require('discord.js'); and then use flags: [MessageFlags.Ephemeral].
Worked example
Assuming the bot is running and the /privateinfo command is registered in your test guild:
- In a Discord text channel, type
/privateinfoand press Enter. - The bot responds with a message that appears only to you, labeled “(ephemeral)” in the official Discord client.
- If you click the message or dismiss it, the ephemeral view disappears; other users never see it.
No additional database or permission changes are required. The message is not persisted in the channel’s history, so it does not contribute to rate‑limit concerns for public messages.
Trade‑off / Limitation
- Interaction‑only: Ephemeral flags work only when replying to an interaction (slash command, button, select menu). You cannot send an ephemeral
channel.sendmessage. - Three‑second window: If you do not call
reply,deferReply, orfollowUpwithin three seconds of receiving the interaction, Discord considers the interaction failed. UsedeferReply({ ephemeral: true })to buy time. - Visibility: Bots and other users cannot see ephemeral responses, so avoid relying on them for cross‑user coordination or for logging that needs to be audited by moderators.
To verify the three‑second behavior, intentionally add a await new Promise(r => setTimeout(r, 4000)) before the reply. You will see the interaction fail with “This interaction failed” in the client unless you first defer.
Actionable closing
If your slash command needs to return private feedback, add the ephemeral flag to your Interaction.reply call. Ensure you have the Guilds and GuildMessages intents, register the command with the applications.commands scope, and handle the three‑second response window with deferReply when necessary. Test by invoking the command in a development server and confirming that only your view shows the message and that it disappears after acknowledgment.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.