Skip to content

Instantly share code, notes, and snippets.

@ivmirx
Forked from rcx/delete-all-messages.js
Last active August 19, 2026 20:03
Show Gist options
  • Select an option

  • Save ivmirx/7d147d35e6a67881e998c74d0fe150ab to your computer and use it in GitHub Desktop.

Select an option

Save ivmirx/7d147d35e6a67881e998c74d0fe150ab to your computer and use it in GitHub Desktop.
Delete all your messages in a Discord channel
/*
* Delete your Discord messages from one channel before a local calendar date.
*
* Run this file in the Discord web app's DevTools Console.
*
* GET THE AUTHORIZATION VALUE IN CURRENT CHROME/DISCORD BUILDS
* -----------------------------------------------------------
* The old localStorage-based automatic method is no longer reliable.
*
* 1. Open Discord in Chrome and open DevTools:
* macOS: Command+Option+I
* Windows/Linux: Ctrl+Shift+I
* 2. Select the Network tab.
* 3. Perform a Discord search so that a `messages/search` request appears.
* 4. Select that request, open Headers, and find Request Headers.
* 5. Copy the complete value of the `Authorization` header.
* 6. Run this script and paste the value into its prompt.
*
* Treat that value like a password. Never put it in this file, commit it to a
* gist, print it in the console, or share it. This script keeps it only in the
* current tab's memory for the duration of the run.
*
* To stop after the current request finishes, run:
* window.__discordMessageWipeStop = true
*/
(async () => {
const CONFIG = {
guildId: "PASTE_GUILD_ID",
channelId: "PASTE_CHANNEL_ID",
authorId: "PASTE_YOUR_USER_ID",
beforeLocalDate: "YYYY-MM-DD",
includeThreads: true,
deleteIntervalMs: 3000,
searchIntervalMs: 1250,
apiVersion: 9
};
if (window.__discordMessageWipeRunning) {
throw new Error("A Discord message wipe is already running in this tab.");
}
if (location.hostname !== "discord.com") {
throw new Error("Run this script only on https://discord.com/app.");
}
for (const field of ["guildId", "channelId", "authorId"]) {
if (!/^\d+$/.test(CONFIG[field])) {
throw new Error(`Set CONFIG.${field} to a Discord numeric ID.`);
}
}
window.__discordMessageWipeRunning = true;
window.__discordMessageWipeStop = false;
try {
const API = `https://discord.com/api/v${CONFIG.apiVersion}`;
const DISCORD_EPOCH = 1420070400000n;
const THREAD_TYPES = new Set([10, 11, 12]);
const sleep = milliseconds =>
new Promise(resolve => setTimeout(resolve, milliseconds));
function parseLocalCalendarDate(value) {
const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(value);
if (!match) {
throw new Error("beforeLocalDate must use YYYY-MM-DD format.");
}
const [, year, month, day] = match.map(Number);
const date = new Date(year, month - 1, day, 0, 0, 0, 0);
if (
date.getFullYear() !== year ||
date.getMonth() !== month - 1 ||
date.getDate() !== day
) {
throw new Error(`Invalid beforeLocalDate: ${value}`);
}
return date;
}
const cutoff = parseLocalCalendarDate(CONFIG.beforeLocalDate);
const cutoffMilliseconds = cutoff.getTime();
const cutoffSnowflake = (
(BigInt(cutoffMilliseconds) - DISCORD_EPOCH) << 22n
).toString();
const authorization = window.prompt(
"Paste the Authorization request-header value from Discord DevTools. " +
"It stays in this tab and is not printed."
)?.trim();
if (!authorization) {
throw new Error("No Authorization value was provided.");
}
const authorizationHeaders = { Authorization: authorization };
async function discordFetch(url, options = {}) {
for (;;) {
const response = await fetch(url, {
...options,
headers: {
...authorizationHeaders,
...(options.headers || {})
}
});
if (response.status === 429) {
const body = await response.json().catch(() => ({}));
const waitMilliseconds =
Math.ceil((Number(body.retry_after) || 1) * 1000) + 250;
console.warn(`Rate limited. Waiting ${waitMilliseconds} ms.`);
await sleep(waitMilliseconds);
continue;
}
// Discord can return 202 while its search index is not ready.
if (response.status === 202) {
const body = await response.json().catch(() => ({}));
const waitMilliseconds = Math.max(
Number(body.retry_after) || 5000,
1000
);
console.warn(
`Discord search is indexing. Waiting ${waitMilliseconds} ms.`
);
await sleep(waitMilliseconds);
continue;
}
return response;
}
}
async function getJson(url) {
const response = await discordFetch(url);
if (!response.ok) {
throw new Error(
`Discord request failed: ${response.status} ${response.statusText}`
);
}
return response.json();
}
// Fail closed if the Authorization value belongs to another account.
const currentUser = await getJson(`${API}/users/@me`);
if (currentUser.id !== CONFIG.authorId) {
throw new Error(
`Wrong Discord account. Expected ${CONFIG.authorId}; ` +
`got ${currentUser.id}.`
);
}
function makeSearchUrl(offset) {
const parameters = new URLSearchParams({
author_id: CONFIG.authorId,
channel_id: CONFIG.channelId,
include_nsfw: "true",
sort_by: "timestamp",
sort_order: "asc",
max_id: cutoffSnowflake,
offset: String(offset)
});
return (
`${API}/guilds/${CONFIG.guildId}/messages/search?` +
parameters.toString()
);
}
console.log("Collecting all matching messages before deleting anything.");
const messagesById = new Map();
let totalResults = Infinity;
for (let offset = 0; offset < totalResults; offset += 25) {
const page = await getJson(makeSearchUrl(offset));
totalResults = Number(page.total_results || 0);
const hits = (page.messages || [])
.flat()
.filter(message => message?.hit === true);
for (const message of hits) {
messagesById.set(message.id, message);
}
console.log(
`Scanned ${Math.min(offset + 25, totalResults)}/${totalResults}.`
);
if (totalResults === 0) break;
await sleep(CONFIG.searchIntervalMs);
}
// A search scoped to a parent channel can include messages in its threads.
// Verify every actual channel before allowing a DELETE request.
const channelSafetyCache = new Map([[CONFIG.channelId, true]]);
async function isAllowedChannel(channelId) {
if (!channelId) return false;
if (channelSafetyCache.has(channelId)) {
return channelSafetyCache.get(channelId);
}
if (!CONFIG.includeThreads) {
channelSafetyCache.set(channelId, false);
return false;
}
try {
const channel = await getJson(`${API}/channels/${channelId}`);
const allowed =
THREAD_TYPES.has(channel.type) &&
channel.parent_id === CONFIG.channelId;
channelSafetyCache.set(channelId, allowed);
return allowed;
} catch (error) {
console.warn(
`Skipping channel ${channelId}: its parent could not be verified.`,
error
);
channelSafetyCache.set(channelId, false);
return false;
}
}
const safeMessages = [];
for (const message of messagesById.values()) {
const correctAuthor = message.author?.id === CONFIG.authorId;
const beforeCutoff =
BigInt(message.id) < BigInt(cutoffSnowflake) &&
Date.parse(message.timestamp) < cutoffMilliseconds;
const correctChannel = await isAllowedChannel(message.channel_id);
if (correctAuthor && beforeCutoff && correctChannel) {
safeMessages.push(message);
}
}
safeMessages.sort((left, right) =>
BigInt(left.id) < BigInt(right.id)
? -1
: BigInt(left.id) > BigInt(right.id)
? 1
: 0
);
console.log(`Verified ${safeMessages.length} messages for deletion.`);
console.log(
"Stop command: window.__discordMessageWipeStop = true"
);
if (safeMessages.length === 0) {
console.log("Nothing matched the configured scope.");
return;
}
const approved = window.confirm(
`Delete ${safeMessages.length} Discord messages?\n\n` +
`Account: ${currentUser.username} (${CONFIG.authorId})\n` +
`Server: ${CONFIG.guildId}\n` +
`Channel: ${CONFIG.channelId}` +
`${CONFIG.includeThreads ? " and its threads" : ""}\n` +
`Before: ${cutoff.toLocaleString()}\n\n` +
"This cannot be undone."
);
if (!approved) {
console.log("Deletion cancelled.");
return;
}
let deleted = 0;
let alreadyGone = 0;
let failed = 0;
for (const message of safeMessages) {
if (window.__discordMessageWipeStop) {
console.warn("Deletion stopped by user.");
break;
}
// Repeat every destructive-action check immediately before DELETE.
const stillSafe =
message.author?.id === CONFIG.authorId &&
BigInt(message.id) < BigInt(cutoffSnowflake) &&
Date.parse(message.timestamp) < cutoffMilliseconds &&
await isAllowedChannel(message.channel_id);
if (!stillSafe) {
failed++;
console.error(`Safety check rejected message ${message.id}.`);
continue;
}
const response = await discordFetch(
`${API}/channels/${message.channel_id}/messages/${message.id}`,
{ method: "DELETE" }
);
if (response.status === 204) {
deleted++;
console.log(
`[${deleted + alreadyGone + failed}/${safeMessages.length}] ` +
`Deleted ${message.id}.`
);
} else if (response.status === 404) {
alreadyGone++;
console.warn(`Message ${message.id} was already gone.`);
} else {
failed++;
console.error(
`Failed to delete ${message.id}: ` +
`${response.status} ${response.statusText}`
);
}
await sleep(CONFIG.deleteIntervalMs);
}
console.log({
matched: safeMessages.length,
deleted,
alreadyGone,
failed,
stopped: window.__discordMessageWipeStop
});
} finally {
window.__discordMessageWipeRunning = false;
}
})().catch(error => {
console.error("Discord message wipe failed:", error);
});

Delete your Discord messages from one channel

This browser-console script deletes only messages that pass all of these checks:

  • The message author is the configured Discord user.
  • The message is in the configured server and channel.
  • If enabled, the message can instead be in a thread whose parent is that channel.
  • The message is older than the configured local calendar date.

Deletion is permanent. Review the confirmation dialog before you continue.

The script uses Discord's internal web API. Discord can change this API without notice. Automated use of a user account can also be subject to Discord's rules. Use the script at your own risk and keep the delays enabled.

1. Configure the script

Edit CONFIG near the top of delete-all-messages.js:

const CONFIG = {
  guildId: "PASTE_GUILD_ID",
  channelId: "PASTE_CHANNEL_ID",
  authorId: "PASTE_YOUR_USER_ID",
  beforeLocalDate: "YYYY-MM-DD",
  includeThreads: true,
  deleteIntervalMs: 3000,
  searchIntervalMs: 1250,
  apiVersion: 9
};
  • guildId: the server ID.
  • channelId: the channel ID. When Discord is open in a browser, both IDs are visible in a URL shaped like https://discord.com/channels/GUILD_ID/CHANNEL_ID.
  • authorId: your Discord user ID. Enable User Settings > Advanced > Developer Mode, then right-click your account and select Copy User ID.
  • beforeLocalDate: the first date to keep, in YYYY-MM-DD format. For example, 2026-07-01 deletes messages before local midnight at the start of 1 July 2026. It does not delete messages sent on or after that time.
  • includeThreads: set this to true to include threads directly under the configured channel, or false to exclude all threads.

Do not put an authorization value in the file.

2. Get the Authorization request-header value

The old local-storage method is no longer reliable in current Chrome and Discord builds.

  1. Open Discord in Chrome and sign in.
  2. Open DevTools with Command+Option+I on macOS or Ctrl+Shift+I on Windows or Linux.
  3. Select the Network tab.
  4. Perform a search in Discord so that a request containing messages/search appears.
  5. Select that request.
  6. Open Headers, then find Request Headers.
  7. Copy the complete value beside Authorization.

Treat this value like a password. Anyone who has it may be able to access your Discord account. Do not share it, save it in the script, print it, or commit it to a gist. If you expose it, sign out of Discord sessions and change your password to invalidate it.

3. Run the script

  1. Keep Discord and DevTools open.
  2. Select the Console tab.
  3. Paste the complete contents of delete-all-messages.js and press Return.
  4. Paste the Authorization value into the prompt.
  5. Check the account, server, channel, thread setting, date, and message count in the confirmation dialog.
  6. Select OK only if the scope is correct.

Chrome can show a self-XSS warning when you paste into DevTools. Follow Chrome's on-screen instruction only if you have read and trust the complete script.

The script first collects all search results. It then verifies the author, cutoff date, and channel for every message before it sends any delete request. It also verifies that the Authorization value belongs to the configured authorId.

Stop a running deletion

Run this in the same Console:

window.__discordMessageWipeStop = true

The script stops after the current request finishes.

Troubleshooting

No Authorization value or an HTTP 401 error

Repeat the Network steps and copy the Authorization request-header value again. Do not copy the header name, quotation marks, or surrounding spaces.

No messages/search request appears

Use Discord's server search box while the Network tab is recording. Filter the request list for search if needed.

Discord reports that search is indexing or rate-limited

The script handles HTTP 202 and 429 responses and waits before retrying. Do not remove or sharply reduce the configured delays.

The script finds no messages

Check all three IDs and the cutoff date. Also check whether the messages are in a thread and whether includeThreads is enabled. Discord search results can lag behind recent changes.

Safety properties

  • The Authorization value is requested at runtime and is not logged or saved.
  • The authenticated account ID must equal authorId.
  • Search is restricted by author, channel, and cutoff snowflake.
  • Every result is checked again by author, timestamp, and channel before deletion.
  • Thread messages are allowed only when Discord reports that their parent is the configured channel.
  • A final confirmation is required before deletion starts.

This script does not support direct messages or server-wide deletion.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment