Google Sheets has App Scripts for processing data, including your Google Sites, Drive, and other services.
Create a new Google Sheet. Name it Site URL List.
Enter column names Page Name and Link
In the top toolbar, click Extensions > Apps Script
Name the project Site URL List Code
Replace myFunction with this enter outputSiteListedLinks. It will scrape your site and output the links to the active Google Sheet:
// Functions generated by Gemini 3.5 Flash (several times...) , modified, and tested
function outputSiteListedLinks() {
// Retrieve the site HTML
const targetUrl= "https://sites.google.com/site/axusdev/";
const response = UrlFetchApp.fetch(targetUrl);
const htmlText = response.getContentText();
const foundPages = new Map();
// Scan the page's internal JSON tracking data using regex
// This extracts all matching subpage path extensions directly from the data block
const jsonPattern = /"\/site\/axusdev\/([^"\/#\s]+)"/g;
let match;
while ((match = jsonPattern.exec(htmlText)) !== null) {
let path = match[1];
// Ignore internal system assets like home configurations or system panels
if (path.includes("system/") || path === "home") continue;
// Format the URL path into a human-readable title
let cleanName = path.replace(/[-_]/g, ' ')
.replace(/\b\w/g, char => char.toUpperCase());
let fullUrl = targetUrl + path;
foundPages.set(fullUrl, cleanName); // Stores in Map to naturally prevent duplicates
}
// Convert the map data back into an array for sorting
const sortedPageData = [["Home", targetUrl]]; // Manually anchor the homepage
foundPages.forEach((name, url) => {
sortedPageData.push([name, url]);
});
// Alphabetize by Page Name column
sortedPageData.sort((a, b) => a[0].localeCompare(b[0]));
// Overwrite the layout data inside your Google Sheet
const targetSheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
targetSheet.clearContents();
// Paste down header blocks
targetSheet.getRange(1, 1, 1, 2).setValues([["Page Name", "Link URL"]]);
// Paste values starting on row 2
if (sortedPageData.length > 0) {
targetSheet.getRange(2, 1, sortedPageData.length, 2).setValues(sortedPageData);
}
}
// Add a new menu option for running outputSiteList
function onOpen() {
const ui = SpreadsheetApp.getUi();
ui.createMenu('⚙️ Custom')
.addItem('Update Site List Now', 'outputSiteListedLinks')
.addToUi();
}
Click the icon to "Save project to Drive".
Return to the Site Directory sheet and reload it.
Click the "⚙️ Custom" menu and seelect "Update Site List Now".
A window will pop up asking for Authorization. Click OK, click Advanced, and select Go to Untitled project (unsafe).
Check "Select all" and click "Continue"
Here's a Javascript and instructions Gemini wrote me to read the Google Site navigation pane, after I expand it manually.
Navigate to your page.
Press Ctrl-Shift-I to open Chrome's Developer tools.
Click the hamburger menu at the top-left, and carefully expand the hierarchy without clicking any links.
Click the Console tab.
Copy and paste this code to the > prompt
Update the sitePath and press Enter:
(function() {
// 1. Target all navigation link elements in order
const navLinks = document.querySelectorAll('nav a[href], [role="navigation"] a[href], .navigation-drawer a[href]');
const results = [];
const uniqueUrls = new Set();
const baseDomain = "https://sites.google.com";
const sitePath = "/site/axusdev"
navLinks.forEach(link => {
let rawUrl = link.getAttribute('href');
if (!rawUrl) return;
// Clean up anchors and trailing Google login codes like ?authuser=0
rawUrl = rawUrl.split('#')[0].split('?')[0];
// Convert relative layout paths into full URLs
let fullUrl = rawUrl.startsWith('http') ? rawUrl : baseDomain + rawUrl;
if (!uniqueUrls.has(fullUrl) && fullUrl.includes(sitePath)) {
uniqueUrls.add(fullUrl);
// Get the nesting depth level from Google's data attribute (defaults to 1 for top-level)
let level = parseInt(link.getAttribute('data-level'), 10) || 1;
// Extract and clean the menu text label
let name = link.innerText.trim() || link.textContent.trim();
if (!name) {
let segments = fullUrl.split('/');
name = segments[segments.length - 1].replace(/[-_]/g, ' ');
}
// Generate a visual branch prefix based on how deep the subpage is nested
let prefix = "";
if (level === 1) {
prefix = "• "; // Top-level item
} else if (level === 2) {
prefix = " └─ "; // Level 2 subpage
} else if (level === 3) {
prefix = " └─ "; // Level 3 subpage (like your WSL item)
} else {
prefix = " ".repeat((level - 1) * 2) + "└─ "; // Catch-all for even deeper nesting
}
results.push({ visualName: prefix + name, url: fullUrl });
}
});
// 2. Clear console and print without sorting (preserves layout order)
console.clear();
console.log("--- HIERARCHICAL SITE NAVIGATION LIST (Copy below this line) ---");
let outputString = results.map(item => `${item.visualName}\t${item.url}`).join('\n');
console.log(outputString);
})();
Right-click and select Copy Console.
Create a Google Doc and name it Site Directory.
Go to Tools > Preferences and turn on Enable Markdown.
Now when you can paste the console output into a Google Doc, it will be formatted as a hierarchical list of links.
While editing your Google Site, you can Insert , scroll down and click Docs, select "Site Directory", and click Insert.
Instead of outputting Markdown, we can output to Tab Separated Values.
Navigate to your Google Site in Chrome browser. Make sure you are viewing the published site, and not editing.
Press Ctrl-Shift-I to open Chrome's Developer tools.
Click the hamburger menu at the top-left, and carefully expand the hierarchy (starting from the bottom) without clicking any links.
Click the Console tab.
Copy and paste this code to the > prompt.
Copy and paste this code to the > prompt
Update the sitePath and press Enter:
(function() {
const navLinks = document.querySelectorAll('nav a[href], [role="navigation"] a[href], .navigation-drawer a[href]');
const results = [];
const uniqueUrls = new Set();
const baseDomain = "https://sites.google.com";
const sitePath = "/site/axusdev"
navLinks.forEach((link, index) => {
let rawUrl = link.getAttribute('href');
if (!rawUrl) return;
// Clean up anchors and trailing Google login codes like ?authuser=0
rawUrl = rawUrl.split('#')[0].split('?')[0];
// Convert relative layout paths into full URLs
let fullUrl = rawUrl.startsWith('http') ? rawUrl : baseDomain + rawUrl;
if (!uniqueUrls.has(fullUrl) && fullUrl.includes(sitePath)) {
uniqueUrls.add(fullUrl);
// Extract the nesting depth level from Google's data attributes
let currentLevel = parseInt(link.getAttribute('data-level'), 10) || 1;
// Peek at the NEXT item in the loop to determine if this link is a parent node
let isParent = false;
if (index + 1 < navLinks.length) {
let nextLink = navLinks[index + 1];
let nextLevel = parseInt(nextLink.getAttribute('data-level'), 10) || 1;
// If the next item is deeper in the tree, this item is a parent node
if (nextLevel > currentLevel) {
isParent = true;
}
}
// Extract and clean the menu text label
let name = link.innerText.trim() || link.textContent.trim();
if (!name) {
let segments = fullUrl.split('/');
name = segments[segments.length - 1].replace(/[-_]/g, ' ');
}
// Apply proper title capitalization
name = name.replace(/\b\w/g, char => char.toUpperCase());
// If it is a parent node, apply bold Markdown markdown formatting and ignore URL
if (isParent) {
name = `**${name}**`;
fullUrl = ' '
}
// Separate Name and URL with a literal Tab character (\t)
results.push(`${name}\t${fullUrl}`);
}
});
// Clear console and print the raw copyable block
console.clear();
console.log("--- COPY BLOCK ---");
console.log(results.join('\n'));
})();
Select the items below "--- COPY BLOCK ---", right-click, and Copy.