Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save astamicu/eb351ce10451f1a51b71a1287d36880f to your computer and use it in GitHub Desktop.

Select an option

Save astamicu/eb351ce10451f1a51b71a1287d36880f to your computer and use it in GitHub Desktop.
Script to remove all videos from Youtube Watch Later playlist

UPDATED 22.11.2022

It's been two years since the last update, so here's the updated working script as per the comments below.

Thanks to BryanHaley for this.

setInterval(function () {
    video = document.getElementsByTagName('ytd-playlist-video-renderer')[0];

    video.querySelector('#primary button[aria-label="Action menu"]').click();

    var things = document.evaluate(
        '//span[contains(text(),"Remove from")]',
        document,
        null,
        XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,
        null
    );

    for (var i = 0; i < things.snapshotLength; i++) 
    {
        things.snapshotItem(i).click();
    }
}, 500);

Non-english users will need to change "Action menu" and "Remove from" to what YouTube uses for their localization.

@akod1ng

akod1ng commented Jun 18, 2025

Copy link
Copy Markdown

Where exactly is everyone posting this script onto? I know its somewhere within the the F12 (inspection) area of the browser but where exactly? Pictures would be greatly appreciated

You have a tab called console in the development tools: https://developer.chrome.com/docs/devtools/console/javascript/

for the first time you might have to follow steps outlined in the console in order to paste a command

@ltkvien

ltkvien commented Oct 1, 2025

Copy link
Copy Markdown

To remove hidden videos. Click the dots in Watch Later playlist and select "Show unavailable videos" then run this version of the script:

setInterval(function () {
    video = document.getElementsByTagName('ytd-playlist-video-renderer')[0];

    video.querySelector('#primary button').click();

    var things = document.evaluate(
        '//span[contains(text(),"Remove from")]',
        document,
        null,
        XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,
        null
    );

    for (var i = 0; i < things.snapshotLength; i++) 
    {
        things.snapshotItem(i).click();
    }
}, 1000);

It worked for me. It was able to remove private/hidden/deleted videos. Thanks a lot!

@John-nata

John-nata commented Feb 11, 2026

Copy link
Copy Markdown

Hey everyone!

Just pushed the 2.0 version of my Youtube Playlist Cleaner script that I mentioned in this convo before.
Link to the script: https://github.com/John-nata/YT-Playlist-Cleaner

image

Big thanks to every single person from this convo who starred my script (over 40!) and reported issues- you made this vrsion way better than I could've done alone!

New Features:

  • Filter for unavailable videos only (private/deleted)
  • Age-based deletion (skip videos from the last N days)
  • 17 new languages added (Swedish, Danish, Norwegian, Finnish, Ukrainian, Romanian, Slovak, Bulgarian, Croatian, Greek, Catalan, Hindi, Thai, Vietnamese, Indonesian, Malay, Filipino)
  • Pause notification now sticks around until you hit Resume

UI Redesign:
Rebuilt the nterface with a YouTube/Material Design vibe- status badges, hover effects with that subtle red glow, theme recolor, sun/moon toggle icons.

Fixes:

  • Pause/resume logic actually works way better now
  • Completion messages show correct counts
  • Cleaned up Polish, Turkish, and Czech translations

Check out the release notes for the full changelog.

Happy YT cleaning guys! 🧹

@leotulipan

Copy link
Copy Markdown

If you first want to download your (e.g. Watch Later) list as a csv first before deleting you can use this: https://gist.github.com/leotulipan/5c40ed1f44aae7d5bca107d99089eae0

@leotulipan

Copy link
Copy Markdown

This script moves videos out of Watch later and into another playlist. Just change the 'newPlaylistName' variable, and you're all set. 👍

Together with Gemini Pro I enhanced this so you can test it out with a couple of videos first and activate/deactive the deletion part

(async function processWatchLaterVideos() {
    // --- CONFIGURATION ---
    const config = {
        targetPlaylistName: 'wlbackup', 
        maxVideosToProcess: 1, // Set to the number of videos you want to process 
        shouldDelete: false,    // True then it also removes from the source playlist
        observationDelay: 1000 // 1-second delay for debugging
    };

    const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));
    console.log(`Starting migration... Watch the screen closely.`);

    let processedCount = 0;

    while (processedCount < config.maxVideosToProcess) {
        // THE FIX: Grab the first video that we haven't processed yet
        const video = document.querySelector('ytd-playlist-video-renderer:not([data-bot-processed="true"])');
        
        if (!video) {
            console.log("No more videos found in the list! Finished.");
            break;
        }

        // Mark this video as processed so we never grab it again, even if it shifts positions
        video.setAttribute('data-bot-processed', 'true');

        const menuBtn = video.querySelector('ytd-menu-renderer yt-icon-button button');
        if (!menuBtn) {
            console.warn("Could not find the action menu button. Stopping.");
            break;
        }
        
        // 1. Open the menu
        console.log(`\n--- Processing Video ${processedCount + 1} ---`);
        menuBtn.click();
        await delay(800 + config.observationDelay); 

        // 2. Click "Save to playlist"
        const menuItems = Array.from(document.querySelectorAll('ytd-menu-service-item-renderer, tp-yt-paper-item'));
        const saveItem = menuItems.find(el => el.innerText.toLowerCase().includes('save to playlist'));
        
        if (!saveItem) {
            console.warn("Could not find 'Save to playlist' option.");
            break;
        }
        saveItem.click();
        await delay(1000 + config.observationDelay); 

        // --- PLAYLIST SELECTION ---
        const titleElements = Array.from(document.querySelectorAll('.ytListItemViewModelTitle, ytd-playlist-add-to-option-renderer yt-formatted-string[id="label"]'));
        const targetNameLC = config.targetPlaylistName.toLowerCase();
        const targetTitleEl = titleElements.find(el => el.innerText.trim().toLowerCase() === targetNameLC);

        // ALERT AND BREAK IF NOT FOUND
        if (!targetTitleEl) {
            const errorMsg = `CRITICAL ERROR: Playlist "${config.targetPlaylistName}" not found in the dialog! Stopping script.`;
            console.error(errorMsg);
            alert(errorMsg); 
            
            document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', bubbles: true }));
            break; 
        }

        // 3. Attempt to check the box
        const newUIButton = targetTitleEl.closest('button.ytListItemViewModelButtonOrAnchor');
        const oldUIContainer = targetTitleEl.closest('ytd-playlist-add-to-option-renderer');

        if (newUIButton) {
            const isChecked = newUIButton.getAttribute('aria-pressed') === 'true';
            if (!isChecked) {
                console.log(`Checking playlist: ${config.targetPlaylistName}`);
                newUIButton.click();
                await delay(config.observationDelay); 
            }
        } else if (oldUIContainer) {
            const checkbox = oldUIContainer.querySelector('tp-yt-paper-checkbox, #checkbox');
            if (checkbox && !checkbox.classList.contains('checked') && checkbox.getAttribute('aria-checked') !== 'true') {
                console.log(`Checking playlist: ${config.targetPlaylistName}`);
                oldUIContainer.click();
                await delay(config.observationDelay); 
            }
        }

        console.log("Closing dialog...");
        document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', bubbles: true }));
        await delay(800 + config.observationDelay); // Added slightly more delay here to let the UI settle

        // 4. Execute Deletion
        if (config.shouldDelete) {
            console.log("Re-opening menu to remove from Watch Later...");
            menuBtn.click();
            await delay(800 + config.observationDelay);

            const remItems = Array.from(document.querySelectorAll('ytd-menu-service-item-renderer'));
            const removeItem = remItems.find(el => el.innerText.trim().toLowerCase().includes('remove from'));
            
            if (!removeItem) {
                console.warn("Could not find 'Remove from Watch later' option.");
                break;
            }
            
            removeItem.click();
            console.log(`SUCCESS: Video ${processedCount + 1} Removed.`);
        } else {
            console.log(`Video ${processedCount + 1} copied (Deletion Skipped).`);
        }
        
        processedCount++;
        await delay(1000); 
    }

    console.log(`\nDone! Successfully processed ${processedCount} video(s).`);
})();

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