Skip to content

Instantly share code, notes, and snippets.

@JamieMason
Last active March 29, 2026 17:35
Show Gist options
  • Select an option

  • Save JamieMason/7580315 to your computer and use it in GitHub Desktop.

Select an option

Save JamieMason/7580315 to your computer and use it in GitHub Desktop.
Unfollow everyone on twitter.com

Unfollow everyone on twitter.com

By @foldleft.bsky.social, see also Unfollow everyone on bsky.app.

  1. Go to https://twitter.com/YOUR_USER_NAME/following
  2. Open the Developer Console. (COMMAND+ALT+I on Mac)
  3. Paste this into the Developer Console and run it
// Unfollow everyone on twitter.com, by Jamie Mason (https://twitter.com/fold_left)
// https://gist.github.com/JamieMason/7580315
//
// 1. Go to https://twitter.com/YOUR_USER_NAME/following
// 2. Open the Developer Console. (COMMAND+ALT+I on Mac)
// 3. Paste this into the Developer Console and run it
//
// Last Updated: 30 October 2023
(() => {
  const $followButtons = '[data-testid$="-unfollow"]';
  const $confirmButton = '[data-testid="confirmationSheetConfirm"]';

  const retry = {
    count: 0,
    limit: 3,
  };

  const scrollToTheBottom = () => window.scrollTo(0, document.body.scrollHeight);
  const retryLimitReached = () => retry.count === retry.limit;
  const addNewRetry = () => retry.count++;

  const sleep = ({ seconds }) =>
    new Promise((proceed) => {
      console.log(`WAITING FOR ${seconds} SECONDS...`);
      setTimeout(proceed, seconds * 1000);
    });

  const unfollowAll = async (followButtons) => {
    console.log(`UNFOLLOWING ${followButtons.length} USERS...`);
    await Promise.all(
      followButtons.map(async (followButton) => {
        followButton && followButton.click();
        await sleep({ seconds: 1 });
        const confirmButton = document.querySelector($confirmButton);
        confirmButton && confirmButton.click();
      })
    );
  };

  const nextBatch = async () => {
    scrollToTheBottom();
    await sleep({ seconds: 1 });

    const followButtons = Array.from(document.querySelectorAll($followButtons));
    const followButtonsWereFound = followButtons.length > 0;

    if (followButtonsWereFound) {
      await unfollowAll(followButtons);
      await sleep({ seconds: 2 });
      return nextBatch();
    } else {
      addNewRetry();
    }

    if (retryLimitReached()) {
      console.log(`NO ACCOUNTS FOUND, SO I THINK WE'RE DONE`);
      console.log(`RELOAD PAGE AND RE-RUN SCRIPT IF ANY WERE MISSED`);
    } else {
      await sleep({ seconds: 2 });
      return nextBatch();
    }
  };

  nextBatch();
})();

This script:

  • Is completely free and has been since 2013.
  • Doesn't try and get you to sign in or take your personal data.
  • Automates your web browser to make it click unfollow buttons, scroll down to reveal more, then do it again.
  • No tricks, all of the code is here so you can see exactly what it does.

If this script was useful and saved you time, maybe kick in for a Coffee to say thanks:

ko-fi

@Mucenio
Copy link
Copy Markdown

Mucenio commented Jan 25, 2024

work

@Zeroexperiencelearner
Copy link
Copy Markdown

Still working, amazing!
Thank you!

@pkfrank
Copy link
Copy Markdown

pkfrank commented Feb 19, 2024

Thanks!

@Varunt016
Copy link
Copy Markdown

works thanks

@MetinSAYGIN
Copy link
Copy Markdown

Still works thanks

@IconicLabHQ
Copy link
Copy Markdown

Thanks man still working

@DevAtShanto
Copy link
Copy Markdown

Working 😍

@BitWizCoder-ol
Copy link
Copy Markdown

Woow works fine. huge thanks.

@cornfieldlabs
Copy link
Copy Markdown

Still works. Thanks a lot!

@amelsen-ops
Copy link
Copy Markdown

The date today is November 19th, 2025. Does this still work.. anyone know?

@JamieMason
Copy link
Copy Markdown
Author

Give it a try

@w121211
Copy link
Copy Markdown

w121211 commented Dec 20, 2025

Tested on 2025/12/20. Works!

@theharshith
Copy link
Copy Markdown

theharshith commented Jan 6, 2026

Tested on 6th Jan 2026. Didnt work on Macos.

Added a few helpful things turn DRY_RUN to false to check compat. Set UNFOLLOW_LIMIT to a number that you want to try.

Thanks to the main author for the helpful script.

Script
// Unfollow everyone on twitter.com, by Jamie Mason (https://twitter.com/fold_left)
// https://gist.github.com/JamieMason/7580315
//
// 1. Go to https://twitter.com/YOUR_USER_NAME/following
// 2. Open the Developer Console. (COMMAND+ALT+I on Mac)
// 3. Paste this into the Developer Console and run it
//
// https://gist.github.com/JamieMason/7580315?permalink_comment_id=5932205#gistcomment-5932205
// IMPORTANT
const DO_NOT_UNFOLLOW = ['usernames_to_avoid_unfollowing'].map(s => s.toLowerCase());
(() => {
  const DRY_RUN = true;
  const UNFOLLOW_LIMIT = 10;

  const FOLLOW_BUTTON_SELECTOR =
    '[data-testid$="-unfollow"], button[aria-label*="Following"], button[aria-label*="Unfollow"]';

  const CONFIRM_BUTTON_SELECTOR =
    '[data-testid="confirmationSheetConfirm"], button[role="menuitem"]';

  const retry = { count: 0, limit: 3 };
  let unfollowedCount = 0;

  const sleep = ms => new Promise(res => setTimeout(res, ms));
  const scrollToBottom = () => window.scrollTo(0, document.body.scrollHeight);

  const extractHandleFromHref = href => {
    if (!href) return null;
    const parts = href.split('?')[0].split('/').filter(Boolean);
    const candidate = parts.at(-1)?.replace(/^@/, '');
    return /^[A-Za-z0-9_]{2,50}$/.test(candidate) ? candidate.toLowerCase() : null;
  };

  const findHandleNearButton = button => {
    const aria = button.getAttribute?.('aria-label') || '';
    const ariaMatch = aria.match(/@([A-Za-z0-9_]{2,50})/);
    if (ariaMatch) return ariaMatch[1].toLowerCase();

    let el = button;
    for (let i = 0; i < 6 && el; i++) {
      for (const a of el.querySelectorAll?.('a[href]') || []) {
        const h = extractHandleFromHref(a.getAttribute('href'));
        if (h) return h;
      }
      el = el.parentElement;
    }

    const container = button.closest('div') || button.parentElement;
    if (container) {
      for (const n of container.querySelectorAll('span, a, div')) {
        const t = (n.textContent || '').trim();
        const m = t.match(/^@([A-Za-z0-9_]{2,50})$/) || t.match(/@([A-Za-z0-9_]{2,50})/);
        if (m) return m[1].toLowerCase();
      }
    }

    return null;
  };

  const shouldSkip = handle => handle && DO_NOT_UNFOLLOW.includes(handle);

  const unfollowSingle = async (button, handle) => {
    if (unfollowedCount >= UNFOLLOW_LIMIT) return false;

    if (DRY_RUN) {
      console.log(`[DRY-RUN] would unfollow (${unfollowedCount + 1}/${UNFOLLOW_LIMIT}): ${handle || '(unknown)'}`);
      unfollowedCount++;
      return true;
    }

    button.scrollIntoView({ block: 'center' });
    await sleep(200);
    button.click();

    await sleep(500);
    const confirm = document.querySelector(CONFIRM_BUTTON_SELECTOR);
    if (confirm) confirm.click();

    await sleep(300);
    unfollowedCount++;
    console.log(`[LIVE] unfollowed (${unfollowedCount}/${UNFOLLOW_LIMIT}): ${handle || '(unknown)'}`);
    return true;
  };

  const processVisibleButtons = async processed => {
    const buttons = [...document.querySelectorAll(FOLLOW_BUTTON_SELECTOR)];

    for (const btn of buttons) {
      if (unfollowedCount >= UNFOLLOW_LIMIT) return true;

      const handle = findHandleNearButton(btn);
      if (handle && processed.has(handle)) continue;
      if (handle) processed.add(handle);

      if (shouldSkip(handle)) {
        console.log(`[PROTECTED] skipping ${handle}`);
        continue;
      }

      await unfollowSingle(btn, handle);
      await sleep(600);
    }

    return false;
  };

  (async () => {
    console.log(
      `START | mode=${DRY_RUN ? 'DRY-RUN' : 'LIVE'} | limit=${UNFOLLOW_LIMIT}`
    );

    const processed = new Set();

    while (retry.count < retry.limit && unfollowedCount < UNFOLLOW_LIMIT) {
      const limitReached = await processVisibleButtons(processed);
      if (limitReached) break;

      retry.count++;
      scrollToBottom();
      await sleep(1500);
    }

    console.log(
      `DONE | mode=${DRY_RUN ? 'DRY-RUN' : 'LIVE'} | total=${unfollowedCount}/${UNFOLLOW_LIMIT}`
    );
  })();
})();

@kougazhang
Copy link
Copy Markdown

It runs well. Great Job! THX

@jemziy
Copy link
Copy Markdown

jemziy commented Mar 29, 2026

works but get limited. have to refresh and rerun few times. thanks!

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