
Sometimes, using PnP PowerShell just isn’t an option. Whether it’s because of restrictions in your environment or you simply need a quick peek at your SharePoint data, you need another way. I often find myself needing a fast report on lists, fields, subsites, etc.
Enter JavaScript! I know, it might sound a bit unconventional, but running JavaScript right in the browser console has come to the rescue more than once. This method gives me a quick and dirty report on the fly—no extra tools required. It’s not the most polished approach out there, but when you need to discover your SharePoint data fast, it really gets the job done.
Below is a fun little script that does the heavy lifting for you. It starts at your current SharePoint site, grabs all the subsites (webs) recursively, and then pulls in every list along with its content types. Once it’s done, it shows you a neat table right in your browser’s console and even downloads a CSV file for you. Super handy when you need a quick report!
/************************************************************** 1) MAIN ENTRY POINT:* Start from the current site, get all webs (subsites),* then get lists from each web, build final data, and export.*************************************************************/fetchWebAndSubsites(_spPageContextInfo.webAbsoluteUrl).then(function(allWebs) {// For each web, retrieve its listsvar allListPromises = allWebs.map(function(web) {return fetchWebLists(web.Url).then(function(lists) {return lists.map(function(list) {var cts = list.ContentTypes && list.ContentTypes.results? list.ContentTypes.results.map(function(ct) { return ct.Name; }).join(", "): "";return {"Site Title": web.Title,"Site URL": web.Url,"List Title": list.Title,"List URL": list.DefaultViewUrl,"Content Types": cts};});});});// Wait until all list data is fetchedreturn Promise.all(allListPromises).then(function(websListData) {// Flatten the array of arraysvar finalData = [].concat.apply([], websListData);// Show table in consoleconsole.table(finalData);// Convert to CSVvar csv = convertToCSV(finalData);// Automatically download CSVdownloadCSV(csv, "AllSubsitesLists.csv");});}).catch(function(error) {console.error("Error retrieving webs/lists:", error);});/************************************************************** 2) RECURSIVELY FETCH ALL SUBSITES (webs)* Starting from a given web URL, gather that web’s info,* then recursively iterate its child webs.*************************************************************/function fetchWebAndSubsites(webUrl) {// We'll collect all webs in an array, including the current onevar allWebs = [];return fetchWebInfo(webUrl).then(function(currentWeb) {// Add the current web to the arrayallWebs.push({ Title: currentWeb.Title, Url: currentWeb.Url });// Now get its direct child websreturn fetchSubWebs(webUrl);}).then(function(subwebs) {// For each child web, recursively call fetchWebAndSubsitesvar childWebPromises = subwebs.map(function(sw) {return fetchWebAndSubsites(sw.Url);});// Wait for all children to return their websreturn Promise.all(childWebPromises);}).then(function(childWebArrays) {// childWebArrays is an array of arrays, flatten themchildWebArrays.forEach(function(arrayOfWebs) {allWebs = allWebs.concat(arrayOfWebs);});return allWebs;});}/*** Fetch the current web’s Title and Url*/function fetchWebInfo(webUrl) {var url = webUrl + "/_api/web?$select=Title,Url";return fetch(url, {method: "GET",headers: { "Accept": "application/json;odata=verbose" }}).then(function(response) {if (!response.ok) {throw new Error("Failed to fetch web info for: " + webUrl);}return response.json();}).then(function(data) {return data.d; // { Title, Url }});}/*** Fetch direct child webs (subsites) of a given web*/function fetchSubWebs(webUrl) {var url = webUrl + "/_api/web/webs?$select=Title,Url";return fetch(url, {method: "GET",headers: { "Accept": "application/json;odata=verbose" }}).then(function(response) {if (!response.ok) {throw new Error("Failed to fetch sub webs for: " + webUrl);}return response.json();}).then(function(data) {return data.d.results; // an array of { Title, Url }});}/************************************************************** 3) FETCH LISTS FOR A GIVEN WEB* We return each list’s Title, DefaultViewUrl, and ContentTypes.*************************************************************/function fetchWebLists(webUrl) {var url = webUrl+ "/_api/web/lists?$select=Title,DefaultViewUrl,ContentTypes/Name"+ "&$expand=ContentTypes";return fetch(url, {method: "GET",headers: { "Accept": "application/json;odata=verbose" }}).then(function(response) {if (!response.ok) {throw new Error("Failed to fetch lists for: " + webUrl);}return response.json();}).then(function(data) {return data.d.results; // array of list objects});}/************************************************************** 4) CSV CONVERSION / DOWNLOAD*************************************************************//*** Converts an array of objects into CSV format.* Automatically wraps fields containing commas or quotes* in quotes and escapes internal quotes.*/function convertToCSV(rows) {if (!rows || !rows.length) {return "";}// Extract headers (keys) from the first itemvar headers = Object.keys(rows[0]);var csvLines = [];// Add header linecsvLines.push(headers.join(","));// Add each rowrows.forEach(function(row) {var values = headers.map(function(header) {var val = row[header] ? row[header].toString() : "";// If the value contains a comma or quote, wrap in quotes & escape quotesif (/[",]/.test(val)) {val = '"' + val.replace(/"/g, '""') + '"';}return val;});csvLines.push(values.join(","));});return csvLines.join("\n");}/*** Initiates a download of the CSV file in the browser.*/function downloadCSV(csvContent, filename) {var blob = new Blob([csvContent], { type: "text/csv;charset=utf-8;" });var link = document.createElement("a");var url = URL.createObjectURL(blob);link.setAttribute("href", url);link.setAttribute("download", filename);link.style.visibility = "hidden";document.body.appendChild(link);link.click();document.body.removeChild(link);URL.revokeObjectURL(url);}
Results:
And that’s it! This approach is a quick and dirty solution that just works. It might not be the most elegant or polished approach out there, but sometimes you need something fast that gets the job done. With just a bit of vanilla JavaScript and the SharePoint REST API, you can pull all your subsites, lists, and content types, display them in a neat console table, and even download a CSV report—all right from your browser.