You can download the entire classroom with this, but its not beginner friendly. If you want the beginner version to just download 1 video at a time, scroll down to the bottom!
This is not a beginner friendly video, but if you follow along Iβll show you how to get around the download protections.
If youβre looking for a simpler solution or have any questions about this process, just leave a comment and Iβll create a follow-up video.

- homebrew (free)
- wget (free)
- Get cookies.txt LOCALLY
- visual studio code (free)
- live server extension for visual studio code (free)
- pants (in order for this process to work you have to be wearing pants unfortunately)
- Put on pants π
- Login to skool & navigate to the classroom
- Save the cookies with Get cookies.txt extension (netscape format)
- Open browser console & paste in the
.js
code, press enter - Create a folder on your desktop called
project
- Put your
cookies.txt
andurls.txt
file in the project folder - Open terminal application and type
cd ~/Desktop/project
- Run the
wget
command to start the process - Remove pants π©² and wait for it to finish
// Extract all lesson URLs from Skool classroom - paste in browser console
function extractAllLessonUrls() {
console.log("π Searching for lesson URLs on this page...");
const urls = new Set();
// Dynamically get base URL from current page
const currentUrl = new URL(window.location.href);
const baseUrl = `${currentUrl.protocol}//${currentUrl.host}${currentUrl.pathname}`;
console.log(`π Auto-detected base URL: ${baseUrl}`);
console.log(`π Current full URL: ${window.location.href}`);
// Method 1: Look for direct links with md= parameters
document.querySelectorAll('a[href*="classroom"]').forEach(link => {
if (link.href.includes('md=')) {
urls.add(link.href);
console.log("Found link:", link.href);
}
});
// Method 2: Look for any links containing md= in any attribute
document.querySelectorAll('*').forEach(el => {
// Check onclick handlers
const onclick = el.getAttribute('onclick');
if (onclick && onclick.includes('md=')) {
const mdMatch = onclick.match(/md=([a-f0-9]+)/);
if (mdMatch) {
const url = `${baseUrl}?md=${mdMatch[1]}`;
urls.add(url);
console.log("Found in onclick:", url);
}
}
// Check data attributes
['data-href', 'data-url', 'data-link', 'data-md'].forEach(attr => {
const value = el.getAttribute(attr);
if (value && value.includes('md=')) {
if (value.startsWith('http')) {
urls.add(value);
} else {
const mdMatch = value.match(/md=([a-f0-9]+)/);
if (mdMatch) {
const url = `${baseUrl}?md=${mdMatch[1]}`;
urls.add(url);
console.log(`Found in ${attr}:`, url);
}
}
}
});
// Check for md= in any attribute value
for (let attr of el.attributes) {
if (attr.value.includes('md=') && !attr.name.startsWith('data-react')) {
const mdMatch = attr.value.match(/md=([a-f0-9]+)/g);
if (mdMatch) {
mdMatch.forEach(match => {
const mdValue = match.replace('md=', '');
const url = `${baseUrl}?md=${mdValue}`;
urls.add(url);
console.log(`Found in ${attr.name}:`, url);
});
}
}
}
});
// Method 3: Look in page source/innerHTML for md= patterns
const pageSource = document.documentElement.innerHTML;
const mdMatches = pageSource.match(/md=([a-f0-9]{32})/g);
if (mdMatches) {
mdMatches.forEach(match => {
const mdValue = match.replace('md=', '');
const url = `${baseUrl}?md=${mdValue}`;
urls.add(url);
});
console.log(`Found ${mdMatches.length} md= patterns in page source`);
}
// Add current page URL if it has md=
if (window.location.href.includes('md=')) {
urls.add(window.location.href);
console.log("Added current page:", window.location.href);
}
const urlArray = Array.from(urls).sort();
console.log(`\nβ
Found ${urlArray.length} unique lesson URLs:`);
urlArray.forEach((url, index) => {
console.log(`${index + 1}. ${url}`);
});
if (urlArray.length === 0) {
console.log("\nβ No lesson URLs found!");
console.log("π‘ Try running this from the main classroom navigation page");
console.log("π‘ Or manually add URLs to the list below");
// Add current URL as fallback
urlArray.push(window.location.href);
}
// Create urls.txt file and download it
const urlText = urlArray.join('\n');
const blob = new Blob([urlText], {type: 'text/plain'});
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'urls.txt';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
console.log("\nπ Downloaded urls.txt with all found URLs");
console.log("π Next steps:");
console.log("1. Check the downloaded urls.txt file");
console.log("2. Add any missing URLs manually if needed");
console.log("3. Use: wget --input-file=urls.txt [other options]");
return urlArray;
}
// Run the extraction
extractAllLessonUrls();
cd ~/Desktop/project
#!/bin/bash
# Complete website scraping with wget
wget \
--input-file=urls.txt \
--page-requisites \
--html-extension \
--convert-links \
--restrict-file-names=windows \
--force-directories \
--no-clobber \
--directory-prefix=./skool_complete \
--load-cookies=cookies.txt \
--user-agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36" \
--wait=2 \
--span-hosts \
--recursive \
--level=5 \
--no-parent
At this point you basically have a copy of what school has on their servers but you have it on your computer so you can access it anytime anywhere and without internet. Simply open the folder in Visual Studio Code and click on one of the HTML files and open with live server.
Here's the easier version, as request by all ye youtubers!

Step 2: Copy and paste this command in your dev tools console (right click > inspect > go to console tab)
/**
* Skool Loom Video Extractor - Final Version
* Extracts video from current lesson using the md query parameter
*
* Usage: Paste this entire script in browser console while on a Skool classroom page
* Then, go subscribe to @devinschumacher on YouTube
*/
(function() {
console.log('π₯ Skool Loom Video Extractor - Final Version');
// Get the md parameter from URL
const urlParams = new URLSearchParams(window.location.search);
const courseId = urlParams.get('md');
if (!courseId) {
console.log('β No "md" parameter found in URL');
alert('This page does not have a lesson ID (md parameter). Make sure you are on a lesson page.');
return;
}
console.log(`π Looking for course with ID: ${courseId}`);
// Function to recursively search for course by ID
function findCourseById(obj, targetId) {
if (!obj || typeof obj !== 'object') return null;
// Direct match
if (obj.id === targetId && obj.metadata && obj.metadata.videoLink) {
return obj;
}
// Check if this object has a course property with matching ID
if (obj.course && obj.course.id === targetId && obj.course.metadata && obj.course.metadata.videoLink) {
return obj.course;
}
// Recursively search all properties
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
const result = findCourseById(obj[key], targetId);
if (result) return result;
}
}
return null;
}
// Get the Next.js data
const nextDataScript = document.getElementById('__NEXT_DATA__');
if (!nextDataScript) {
console.log('β Could not find __NEXT_DATA__ script');
alert('Could not find page data. This script works on Skool classroom pages.');
return;
}
try {
const nextData = JSON.parse(nextDataScript.textContent);
console.log('π Found page data, searching for course...');
// Search for the course
const course = findCourseById(nextData, courseId);
if (!course) {
console.log('β Could not find course with ID:', courseId);
alert(`Could not find lesson with ID: ${courseId}`);
return;
}
// Extract video information
const metadata = course.metadata;
const videoUrl = metadata.videoLink.split('?')[0]; // Clean URL without parameters
const title = metadata.title || 'Untitled Lesson';
const duration = metadata.videoLenMs ? Math.round(metadata.videoLenMs / 1000) : null;
console.log('β
Found video!');
console.log(`πΉ Title: ${title}`);
console.log(`π URL: ${videoUrl}`);
if (duration) {
const minutes = Math.floor(duration / 60);
const seconds = duration % 60;
console.log(`β±οΈ Duration: ${minutes}:${seconds.toString().padStart(2, '0')}`);
}
// Create download content
const urlContent = videoUrl;
const summaryContent = `# Skool Loom Video
# Course ID: ${courseId}
# Title: ${title}
# Duration: ${duration ? `${Math.floor(duration/60)}:${(duration%60).toString().padStart(2, '0')}` : 'Unknown'}
# Extracted: ${new Date().toLocaleString()}
${videoUrl}
# To download:
# yt-dlp "${videoUrl}"`;
// Download function
function download(content, filename) {
const blob = new Blob([content], { type: 'text/plain' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
}
// Create filename based on title (sanitized)
const safeTitle = title.replace(/[^a-z0-9]/gi, '_').toLowerCase();
const shortTitle = safeTitle.substring(0, 30);
// Download files
download(urlContent, `loom_${shortTitle}.txt`);
setTimeout(() => {
download(summaryContent, `loom_${shortTitle}_info.txt`);
}, 500);
// Success message
setTimeout(() => {
alert(`β
Found video: "${title}"\n\nDownloaded:\nβ’ loom_${shortTitle}.txt\nβ’ loom_${shortTitle}_info.txt\n\nTo download:\nyt-dlp --batch-file loom_${shortTitle}.txt`);
}, 1000);
} catch (e) {
console.error('β Error parsing page data:', e);
alert('Error parsing page data. See console for details.');
}
})();
This will download something to your desktop on Mac.
It will look something like this:
# Skool Loom Video
# Course ID: c22bf3e55b6f41f98e12eedfbb14391e
# Title: How We Notify You If You Won
# Duration: 1:23
# Extracted: 5/28/2025, 2:13:17 PM
https://www.loom.com/share/b19b38d0ce2b44f5a049b871d9a38925
# To download:
# yt-dlp "https://www.loom.com/share/b19b38d0ce2b44f5a049b871d9a38925"
Focus on the yt-dlp "https://www.loom.com/share/b19b38d0ce2b44f5a049b871d9a38925"
part.
Paste that in your terminal and it will download to where you are!
So if that helped, and you're feelin generous - feel free to sponsor me so I can make more tutorials like this.
....and take baths like this