Unlike all your posts on X (Twitter)

Remove all your likes on X, or only your most recent ones, without giving any app access to your account.

unlike-all.js · 101 linespaste in the DevTools console on x.com
// Responsive Muse · Unlike All Posts on X (Twitter) · v1.1 · 2026-10-22
// Removes your likes, newest first: all of them, or only the latest LIMIT. Runs only on
// x.com/i/history/likes. It asks before changing anything, then clicks the same heart button
// you would, at a human pace. It makes no network requests of its own and never reads
// cookies, passwords or tokens.
(async () => {
  const TOOL = 'Unlike all posts';
  const LIMIT = 0;        // only the latest this many posts (0 = all)
  const DELAY = 1500;     // pause between unlikes, in milliseconds. X limits fast actions: don't go lower.

  if (!/^(x|twitter)\.com$/.test(location.hostname) || location.pathname !== '/i/history/likes') {
    alert('Open your likes first (signed in):\nhttps://x.com/i/history/likes');
    return;
  }
  // A small box on the page shows progress and asks before changing anything,
  // so you never need to read or edit the console.
  const many = (n, word) => `${n} ${word}${n === 1 ? '' : 's'}`;   // 1 video, 2 videos
  const panel = (() => {
    const box = document.createElement('div');
    box.style.cssText = 'position:fixed;right:16px;bottom:16px;z-index:2147483647;width:340px;max-width:calc(100vw - 32px);' +
      'background:#111318;color:#e6e8ee;font:14px/1.5 system-ui,sans-serif;border-radius:12px;padding:16px;' +
      'box-shadow:0 8px 30px rgba(0,0,0,.4);text-align:left';
    const head = document.createElement('strong');
    head.textContent = TOOL;
    const msg = document.createElement('p');
    msg.style.cssText = 'margin:8px 0 12px;white-space:pre-line';
    const row = document.createElement('div');
    row.style.cssText = 'display:flex;flex-wrap:wrap;gap:8px;justify-content:flex-end';
    box.append(head, msg, row);
    document.body.append(box);
    const button = (label, primary, onClick) => {
      const b = document.createElement('button');
      b.textContent = label;
      b.style.cssText = 'font:600 14px system-ui,sans-serif;padding:8px 14px;border-radius:8px;cursor:pointer;' +
        (primary ? 'background:#0f766e;color:#fff;border:1px solid #0f766e' : 'background:none;color:#e6e8ee;border:1px solid #3a3f4b');
      b.onclick = onClick;
      row.append(b);
    };
    return {
      text(t) { msg.textContent = t; },
      ask(t, yes) {
        msg.textContent = t;
        row.replaceChildren();
        return new Promise(r => {
          button('Cancel', false, () => { row.replaceChildren(); r(false); });
          button(yes, true, () => { row.replaceChildren(); r(true); });
        });
      },
      stoppable(onStop) { row.replaceChildren(); button('Stop', false, onStop); },
      actions(t, list) {
        msg.textContent = t;
        row.replaceChildren();
        list.forEach(([label, primary, run]) => button(label, primary, run));
      },
      finish(t) { this.actions(t, [['Close', true, () => box.remove()]]); },
      close() { box.remove(); },
    };
  })();
  const sleep = ms => new Promise(r => setTimeout(r, ms));
  const idOf = art => ((art.querySelector('a[href*="/status/"] time')?.closest('a')?.getAttribute('href') || '')
    .match(/\/status\/(\d+)/) || [])[1];
  const done = new Set();   // X keeps unliked posts on screen, so remember which ones are handled

  // 1. Ask (X loads likes as you scroll, so the total isn't known in advance)
  const scope = LIMIT ? `your latest ${LIMIT} likes` : 'all your likes, newest first';
  if (!await panel.ask(`Unlike ${scope}?\nThis can't be undone. You can stop at any time.`, LIMIT ? `Unlike ${LIMIT}` : 'Unlike all')) {
    panel.finish('Cancelled. Nothing was changed.');
    return 0;
  }

  // 2. Unlike one by one, scrolling to load more
  let stop = false;
  panel.stoppable(() => { stop = true; });
  window.scrollTo(0, 0);
  await sleep(DELAY);
  let count = 0, idle = 0;
  while (!stop && idle < 4 && !(LIMIT && count >= LIMIT)) {
    const art = [...document.querySelectorAll('article[data-testid="tweet"]')]
      .find(a => a.querySelector('button[data-testid="unlike"]') && !done.has(idOf(a)));
    if (!art) {
      window.scrollBy(0, window.innerHeight * 0.8);   // load more
      await sleep(DELAY);
      idle++;
      continue;
    }
    idle = 0;
    done.add(idOf(art));
    art.scrollIntoView({ block: 'center' });
    art.querySelector('button[data-testid="unlike"]').click();
    count++;
    panel.text(`Unliking… ${count}${LIMIT ? ` of ${LIMIT}` : ''}`);
    await sleep(DELAY);
  }
  if (!count && !stop) {
    panel.finish('You have no liked posts to unlike.');
    return 0;
  }
  panel.finish(`${stop ? 'Stopped' : 'Done'}: unliked ${many(count, 'post')}.\nReload the page to see the result.`);
  return count;
})();

Bookmarklet: drag the middle button to your bookmarks bar, open x.com and click the bookmark. It runs the same script with the options above.

Why it's safe to runAutomated check passed Oct 22, 2026 · tested on a real account Oct 22, 2026
  • Sends nothing anywhere. No fetch, XHR or external scripts. The only network activity is x.com itself loading your list.
  • Never touches your login. It doesn't read cookies, passwords, tokens or browser storage.
  • Only runs on x.com. Anywhere else it stops before doing anything.
  • Short and readable. 101 commented lines, not minified or obfuscated.
  • Asks before it changes anything. A box on the page shows what it found and waits for your OK. You can stop it at any time.
  • Same code for everyone. SHA-256 of the default version: d8c694d16c5457a9…
Verify it yourself in 30 seconds
  1. Read it: every line is above, with comments.
  2. Search the code for fetch, XMLHttpRequest, cookie, localStorage or eval. None of them appear.
  3. Open the Network tab before running it: you'll only see requests to x.com.

Good to know: this can't be undone in bulk. The script always shows the count and asks before it starts. Leave the tab open until the box says "Done".

Did it work for you?

How to use it

  1. Open x.com/i/history/likes while signed in.
  2. Open the console: F12 or Ctrl+Shift+J (Cmd+Option+J on Mac). If Chrome asks, type allow pasting and press Enter.
  3. Choose “Only the latest” if you want a partial cleanup, copy the script, paste it in the console and press Enter. A small box appears on the page: click Unlike to confirm. You can stop it at any time.

A safe pace

The script clicks the heart button on each liked post, newest first, waiting 1.5 seconds between clicks. That is slower than paid tools but keeps your account clear of X’s limits on automated activity. In our test, “Only the latest 5” removed exactly the five newest likes and left every older like untouched.

FAQ

Will X block my account?

X limits accounts that act too fast. The script waits 1.5 seconds between unlikes, like a person would. For thousands of likes, run it in batches with "Only the latest".

Can I remove only recent likes?

Yes. Likes are listed newest first, so "Only the latest 50" removes your 50 most recent likes and leaves the rest.

Do people get notified?

No. Removing a like doesn't notify anyone.

More free tools

Clean up

Export & back up