/bin/scripts/purge_perplexity.js
I love Perplexity. It has effectively replaced Google for me. But my history is a graveyard of half-baked thoughts, random debugging queries, and "how to center a div" searches that I don't need to see again. So? I've created a simple bookmarklet that will delete your entire perplexity threads history. Feel free to copy the code below, or read the rest of the article to learn how it works.
The Tool
Option 1: The Bookmarklet
Fast, one‑click cleanup once it's set up.
- Log in to perplexity.ai and click Copy Bookmarklet Code.
- Create a new bookmark and paste the code into the URL field.
- Name it (for example Clear Perplexity) and click it while on perplexity.ai.
Option 2: The Source Code
For those who prefer to see everything. You get the full, unminified script that the bookmarklet runs.
- Open your browser's developer console (F12).
- Click Copy Source Code to copy the script.
- Paste it into the console and press Enter.
- Follow the on‑screen prompts.
If the bookmarklet ever stops working, you can always use Copy Source Code instead and run it directly in your browser console.
The Discovery
It turns out, the internal API is surprisingly permissive. There are no rate limits on fetching your thread lists, and the batch deletion endpoint works like a charm if you feed it the right UUIDs.
I wrote a script to automate this. I tested it on my own account and cleared 420 threads in under 10 seconds. It felt therapeutic.
Under the Hood
For the developers out there, here is how it works.
The Constants & Setup
We start by defining the environment. The API_VERSION is critical—Perplexity's backend expects this specific version string. We also set a conservative batch size of 50 threads to avoid timeouts, though I've tested it higher.
const API_VERSION = "2.18";
const BASE_URL = "https://www.perplexity.ai/rest";
const BATCH_SIZE = 50;
const BATCH_DELAY_MS = 500;
The UI Injection
Since we're running in the console context, we can't rely on React or the existing page structure. We have to inject our own UI. I created a self-contained overlay using standard DOM APIs and injected a style block to make it match the site's dark theme.
function createOverlay() {
// ... removal of existing overlay ...
overlay = document.createElement('div');
overlay.id = 'perplexity-cleaner-overlay';
// ... innerHTML construction for modal ...
const style = document.createElement('style');
style.textContent = `#perplexity-cleaner-overlay { ... }`; // CSS styles
document.head.appendChild(style);
document.body.appendChild(overlay);
// ... grabbing references to elements ...
}
The API Client
This is the heavy lifter. We wrap fetch to include the specific headers Perplexity requires. The x-app-apiversion and x-perplexity-request-reason headers are necessary to avoid 403 Forbidden errors. We also mimic the browser's sec-ch-ua headers to look like a legitimate client.
function apiCall(url, method, body, reason) {
return fetch(url, {
method,
headers: {
"content-type": "application/json",
"x-app-apiversion": API_VERSION, // Critical for auth
"x-perplexity-request-endpoint": url,
"x-perplexity-request-reason": reason,
// ... standard browser headers ...
},
body: body ? JSON.stringify(body) : undefined,
// ... credentials: "include" is vital for passing cookies ...
}).then(r => r.ok ? r.json() : Promise.reject(new Error(`${r.status}`)));
}
The Discovery Loop
Perplexity paginates their thread list. We can't just "get all." So, we set up a loop that requests pages of 20 threads at a time, incrementing the offset until the API tells us has_next_page is false.
async function fetchAllThreads() {
const threads = [];
let offset = 0;
while (true) {
const pageThreads = await apiCall(
`${BASE_URL}/thread/list_ask_threads?version=${API_VERSION}&source=default`,
"POST",
{ limit: 20, ascending: false, offset, search_term: "" },
"threads-body"
);
if (!Array.isArray(pageThreads) || pageThreads.length === 0) break;
threads.push(...pageThreads);
// Update UI with progress
updateState('🔍', 'Scanning', `Found ${threads.length} threads...`);
if (!pageThreads[pageThreads.length - 1]?.has_next_page) break;
offset += 20;
}
return threads;
}
The Deletion Logic
Once we have the UUIDs, we batch them into groups of 50. This prevents the server from choking on a massive request payload. We also add a slight 500ms delay between batches to be polite to their rate limiters (if they exist).
async function deleteThreads(uuids) {
// ... UI updates ...
let deleted = 0;
for (let i = 0; i < uuids.length; i += BATCH_SIZE) {
const result = await deleteThreadBatch(uuids.slice(i, i + BATCH_SIZE));
if (result.status === "success") {
deleted += Math.min(BATCH_SIZE, uuids.length - i);
// Update progress bar percentage
updateState('🗑️', 'Deleting...',
`${deleted}/${uuids.length} threads cleared`,
(deleted / uuids.length) * 100
);
}
// Polite delay
if (i + BATCH_SIZE < uuids.length)
await new Promise(r => setTimeout(r, BATCH_DELAY_MS));
}
// ... completion state ...
}
I will be publishing more useful tools like this in the future, hope this helps someone.
Warning
This script is a blunt instrument. It does not ask "are you sure?" for individual threads. Once you confirm the total count, it deletes everything. There is no undo button, no trash can, and no recovery.
Use it wisely.
<terminate_session />