Skip to content

Instantly share code, notes, and snippets.

@JamieMason
Last active January 15, 2026 01:20
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

@imsaifulislam
Copy link

Thanks :) Its working FIne

@Mucenio
Copy link

Mucenio commented Jan 25, 2024

work

@Zeroexperiencelearner
Copy link

Still working, amazing!
Thank you!

@pkfrank
Copy link

pkfrank commented Feb 19, 2024

Thanks!

@Varunt016
Copy link

works thanks

@MetinSAYGIN
Copy link

Still works thanks

@IconicLabHQ
Copy link

Thanks man still working

@DevAtShanto
Copy link

Working 😍

@BitWizCoder-ol
Copy link

Woow works fine. huge thanks.

@cornfieldlabs
Copy link

Still works. Thanks a lot!

@amelsen-ops
Copy link

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

@JamieMason
Copy link
Author

Give it a try

@w121211
Copy link

w121211 commented Dec 20, 2025

Tested on 2025/12/20. Works!

@theharshith
Copy link

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}`
    );
  })();
})();

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