Skip to content

Instantly share code, notes, and snippets.

@ajohnclark
Last active October 14, 2025 13:20
Show Gist options
  • Select an option

  • Save ajohnclark/8015c38015f66c8025b965e0c233cb53 to your computer and use it in GitHub Desktop.

Select an option

Save ajohnclark/8015c38015f66c8025b965e0c233cb53 to your computer and use it in GitHub Desktop.
Unfollow all on X.
// Unfollow all on x.com (working as of 10/2025). Run in dev console on /following page. May break / need to run a few times, janky.
(async function unfollowAll({
clickDelay = 700, // ms between individual clicks
scrollDelay = 1200, // ms to wait after scrolling to load items
max = 0 // 0 = unlimited, set >0 to stop after that many unfollows
} = {}) {
const sleep = ms => new Promise(res => setTimeout(res, ms));
// selector targets buttons that look like the "Following" buttons in the new HTML
const selector = 'button[data-testid$="-unfollow"], button[aria-label^="Following"]';
// return only visible buttons that we haven't already marked/clicked
function getVisibleButtons() {
return Array.from(document.querySelectorAll(selector))
.filter(b => b.offsetParent !== null && !b.dataset.__unfollow_script_clicked);
}
// if an "Are you sure?" confirmation pops up, try to click an Unfollow/Confirm button
function clickConfirmDialog() {
// look for common labels in buttons inside dialogs
const candidates = Array.from(document.querySelectorAll('div[role="dialog"] button, button'));
const confirm = candidates.find(btn => /\bunfollow\b/i.test(btn.textContent));
if (confirm) {
try { confirm.click(); return true; } catch(e) {}
}
return false;
}
let clicked = 0;
let consecutiveScrollsWithoutNew = 0;
const MAX_SCROLL_ATTEMPTS = 6; // how many scroll attempts when no new buttons appear
console.log('Unfollow script started (clickDelay=%sms, scrollDelay=%sms, max=%s)', clickDelay, scrollDelay, max);
while (true) {
let buttons = getVisibleButtons();
if (buttons.length === 0) {
// no visible buttons — try to load more by scrolling
window.scrollBy(0, window.innerHeight);
await sleep(scrollDelay);
const buttonsAfter = getVisibleButtons();
if (buttonsAfter.length === 0) {
consecutiveScrollsWithoutNew++;
} else {
consecutiveScrollsWithoutNew = 0;
}
// if we've tried scrolling several times and still nothing, assume we're done
if (consecutiveScrollsWithoutNew >= MAX_SCROLL_ATTEMPTS) {
console.log('No more unfollow buttons found after scrolling. Exiting.');
break;
}
continue;
}
// click through the visible buttons (one-by-one)
for (const btn of buttons) {
// mark so we don't re-click it if DOM doesn't change immediately
btn.dataset.__unfollow_script_clicked = '1';
try {
btn.click();
clicked++;
// if a confirmation appears, try to accept it
await sleep(250);
clickConfirmDialog();
} catch (err) {
console.warn('click failed', err);
}
console.log('Clicked unfollow #' + clicked, btn);
// obey max
if (max > 0 && clicked >= max) {
console.log('Reached max limit (' + max + '). Stopping.');
return;
}
await sleep(clickDelay);
}
}
console.log('Finished. Total unfollow clicks: ' + clicked);
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment