Skip to content

Instantly share code, notes, and snippets.

@astamicu
Last active September 4, 2026 00:42
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.

@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