One-click tools for technical SEO audits, on-page analysis, and web-performance inspection β straight from your bookmarks bar.
A curated, dependency-free collection of 37 bookmarklets that turn any web page into an instant SEO and performance audit surface. No browser extensions, no tracking, no build step β every tool is a single piece of readable JavaScript you can inspect, copy, and adapt.
Built for SEO consultants, technical SEOs, and anyone teaching or learning how the web is put together.
A bookmarklet is a small piece of JavaScript saved as a bookmark in your browser. When clicked, it runs against the current page to perform a specific action β modify the page, extract data, or open a related tool. They're a fast, install-free way to customize your browsing and automate repetitive checks.
Everything in this repository is geared toward SEO and performance auditing. Each tool is also available as an individual file under bookmarklets/, and the full catalog lives in bookmarklets/manifest.json.
Open the install page and drag any tool straight onto your bookmarks bar.
- Show your bookmarks bar β Chrome: β/Ctrl + Shift + B.
- Drag the purple Install button onto the bar.
- Open any page and click the bookmark to run the tool.
- Copy the JavaScript for the tool you want (from this README or the
bookmarklets/folder). - Create a new bookmark in Chrome.
- Paste the code into the bookmark's URL field, give it a name, and save.
Chrome stores a bookmarklet as the bookmark's URL, and very long scripts get truncated. Tools marked DevTools snippet (e.g. the Capo <head> order tool) are too large to be reliable bookmarklets β run them as a snippet instead:
- Open DevTools (F12 or β/Ctrl + Shift + I) β Sources β Snippets β + New snippet.
- Paste the code and press β/Ctrl + Enter to run it (or right-click β Run).
- Alternatively, paste the code straight into the Console (β/Ctrl + Shift + J).
π‘ Tools marked Console print their output to the DevTools Console. Open it with β/Ctrl + Shift + J to see the results.
Open Google's testing and reporting tools pre-filled with the current page or domain.
Opens the GSC performance report filtered to the current page using the "URLs containing" filter.
Requires access to the matching URL-prefix property in the Google Search Console account for the current domain.
π bookmarklets/google-tools/gsc-urls-containing.js
javascript: (function() {
window.open(`https://search.google.com/search-console/performance/search-analytics?resource_id=${encodeURIComponent(window.location.origin)}&page=*${encodeURIComponent(window.location.href)}`)
})();Opens the GSC performance report filtered to the current page using the "Exact URL" filter.
Requires access to the matching URL-prefix property in the Google Search Console account for the current domain.
π bookmarklets/google-tools/gsc-exact-url.js
javascript: (function() {
window.open(`https://search.google.com/search-console/performance/search-analytics?resource_id=${encodeURIComponent(window.location.origin)}&page=!${encodeURIComponent(window.location.href)}`)
})();Sends the current URL to Google's Rich Results Test.
π bookmarklets/google-tools/rich-results-test.js
javascript: (function() {
window.open(`https://search.google.com/test/rich-results?url=${encodeURIComponent(window.location.href)}`)
})();Sends the current URL to Google's AMP Test.
π bookmarklets/google-tools/amp-test.js
javascript: (function() {
window.open(`https://search.google.com/test/amp?url=${encodeURIComponent(window.location.href)}`)
})();Runs PageSpeed Insights against the current URL.
π bookmarklets/google-tools/pagespeed-insights.js
javascript: (function() {
window.open(`https://pagespeed.web.dev/analysis?url=${encodeURIComponent(window.location.href)}`)
})();Opens the Chrome UX Report dashboard for the current domain.
π bookmarklets/google-tools/crux-report.js
javascript: (function() {
window.open(`https://lookerstudio.google.com/u/0/reporting/c450f8df-caf7-4d3c-a2aa-12dea55122bf/page/keDQB?params=%7B%22origin%22:%22${encodeURIComponent(window.location.hostname)}%22%7D`)
})();Opens the CrUX Vis visualisation for the current origin.
π bookmarklets/google-tools/crux-vis-report.js
javascript: (function() {
window.open(`https://cruxvis.withgoogle.com/#/?view=allmetrics&url=${encodeURIComponent(window.location.origin)}%2F&identifier=origin&device=PHONE&periodStart=0&periodEnd=-1&display=p75s`)
})();Runs a site: search on Google for the current domain (ignoring www.) in a new tab.
π bookmarklets/google-tools/site-search.js
javascript: (function() {
let domain = window.location.hostname.replace('www.', '');
let searchQuery = encodeURIComponent(`site:${domain}`);
window.open(`https://www.google.com/search?q=${searchQuery}`, '_blank');
})();Inspect the on-page signals that shape how a page is understood and indexed.
Shows the canonical URL and meta robots directive of the current page.
π bookmarklets/on-page-seo/canonical-and-robots.js
javascript:(function() {
function getMetaContentByName(name) {
var metaTag = document.querySelector('meta[name="' + name + '"]');
return metaTag ? metaTag.getAttribute('content') : null;
}
var canonicalUrl = getMetaContentByName('canonical');
var canonicalMessage = canonicalUrl ? 'Canonical URL:' + '\n' + canonicalUrl : 'Canonical URL does not exist! π€';
var metaRobots = getMetaContentByName('robots');
var robotsMessage = metaRobots ? 'Meta Robots:' + '\n' + metaRobots : 'Meta Robots does not exist! π€';
alert(canonicalMessage + '\n' + '\n' + robotsMessage);
})();Shows the meta description of the current page.
π bookmarklets/on-page-seo/meta-description.js
javascript:(function() {
var metaDescriptionTag = document.querySelector('meta[name="description"]');
if (metaDescriptionTag) {
var metaDescription = metaDescriptionTag.content;
alert('Meta Description:\n' + metaDescription);
} else {
alert('No meta description found on this page.');
}
})();Shows the title tag, meta description, and meta keywords alongside their character counts.
π bookmarklets/on-page-seo/title-description-keywords.js
javascript:(function() {
var title = document.title;
var metaDescription = document.querySelector('meta[name="description"]');
var metaKeywords = document.querySelector('meta[name="keywords"]');
var titleLength = title ? title.length : 0;
var descriptionLength = metaDescription ? metaDescription.content.length : 0;
var keywordsLength = metaKeywords ? metaKeywords.content.length : 0;
var output = 'Title: ' + title + '\nCharacters: ' + titleLength + '\n\n';
output += 'Meta Description: ' + (metaDescription ? metaDescription.content : 'N/A') + '\nCharacters: ' + descriptionLength + '\n\n';
output += 'Meta Keywords: ' + (metaKeywords ? metaKeywords.content : 'N/A') + '\nCharacters: ' + keywordsLength;
alert(output);
})();Labels every heading on the page with its tag (h1βh6) so you can audit the document outline at a glance.
π bookmarklets/on-page-seo/heading-levels.js
javascript: (function() {
var headings = document.querySelectorAll('h1, h2, h3, h4, h5, h6');
headings.forEach(function(heading) {
var headingTypeContainer = document.createElement('div');
headingTypeContainer.style = 'position: relative; display: inline-block; margin-left: 10px;';
var headingTypeLabel = document.createElement('div');
headingTypeLabel.style = 'position: absolute; top: -20px; right: 0; background-color: #5e4899; color: #adf0d6; padding: 5px;border-radius:5px;';
headingTypeLabel.textContent = heading.tagName.toLowerCase();
headingTypeContainer.appendChild(headingTypeLabel);
heading.appendChild(headingTypeContainer);
});
})();Overlays the alt attribute of every image directly on the page.
π bookmarklets/on-page-seo/image-alt-text.js
javascript: (function() {
var images = document.querySelectorAll('img');
images.forEach(function(img) {
var altText = img.alt || 'N/A';
var altOverlay = document.createElement('div');
altOverlay.style.position = 'absolute';
altOverlay.style.top = '0';
altOverlay.style.left = '0';
altOverlay.style.background = 'rgba(255, 255, 255, 0.9)';
altOverlay.style.padding = '5px';
altOverlay.style.border = '1px solid #ccc';
altOverlay.style.zIndex = '9999';
altOverlay.textContent = 'Alt Text: ' + altText;
img.style.position = 'relative';
img.parentNode.insertBefore(altOverlay, img);
});
})();Counts <br> tags and highlights the 20 characters before and after each one β handy for spotting line breaks misused for layout in body copy.
π bookmarklets/on-page-seo/br-tags.js
javascript:(function() {
var brTags = document.querySelectorAll('br');
if (brTags.length > 0) {
brTags.forEach(function(brTag) {
var previousText = getSurroundingText(brTag, 20, 'before');
var followingText = getSurroundingText(brTag, 20, 'after');
highlightText(brTag, previousText, followingText);
});
alert('Found and highlighted text around <br> tags.');
} else {
alert('No <br> tags found on this page.');
}
function getSurroundingText(element, characters, direction) {
var surroundingText = '';
var currentNode = element[direction === 'before' ? 'previousSibling' : 'nextSibling'];
while (currentNode && characters > 0) {
if (currentNode.nodeType === 3) {
var text = currentNode.textContent;
surroundingText = (direction === 'before' ? text.slice(-characters) : text.slice(0, characters)) + surroundingText;
characters -= text.length;
} else if (currentNode.nodeType === 1) {
surroundingText = currentNode.textContent + surroundingText;
}
currentNode = element[direction === 'before' ? 'previousSibling' : 'nextSibling'];
}
return surroundingText;
}
function highlightText(element, beforeText, afterText) {
var parent = element.parentNode;
var beforeSpan = document.createElement('span');
beforeSpan.textContent = beforeText;
beforeSpan.style.backgroundColor = 'red';
var brSpan = document.createElement('span');
brSpan.textContent = '<br>';
brSpan.style.backgroundColor = 'lightgreen';
var afterSpan = document.createElement('span');
afterSpan.textContent = afterText;
afterSpan.style.backgroundColor = 'orange';
parent.insertBefore(beforeSpan, element);
parent.insertBefore(brSpan, element);
parent.insertBefore(afterSpan, element);
parent.removeChild(element);
}
})();Summarises total elements, resources, page weight, meta tags, headings (h1βh6), links, CSS/JS, canonical, images, and more in a single alert.
π bookmarklets/on-page-seo/page-element-overview.js
javascript: (function() {
var elementsCount = document.querySelectorAll('*').length;
var resourcesCount = window.performance.getEntries().length;
var totalSize = 0;
window.performance.getEntries().forEach(function(entry) {
totalSize += entry.encodedBodySize || 0;
});
var totalSizeFormatted = (totalSize / (1024 * 1024)).toFixed(2) + ' MB';
var metaTagsCount = document.querySelectorAll('meta').length;
var headingsCount = document.querySelectorAll('h1, h2, h3, h4, h5, h6').length;
var h1Count = document.querySelectorAll('h1').length;
var h2Count = document.querySelectorAll('h2').length;
var h3Count = document.querySelectorAll('h3').length;
var h4Count = document.querySelectorAll('h4').length;
var h5Count = document.querySelectorAll('h5').length;
var h6Count = document.querySelectorAll('h6').length;
var anchorTagsCount = document.querySelectorAll('a').length;
var cssTagsCount = document.querySelectorAll('link[rel="stylesheet"]').length;
var jsTagsCount = document.querySelectorAll('script[src]').length;
var canonicalTagsCount = document.querySelectorAll('link[rel="canonical"]').length;
var imgCount = document.querySelectorAll('img').length;
var pictureCount = document.querySelectorAll('picture').length;
var videoCount = document.querySelectorAll('video').length;
var breakCount = document.querySelectorAll('br').length;
alert('Elements: ' + elementsCount + ' | ' + 'Resources: ' + resourcesCount + ' | ' + 'Size: ' + totalSizeFormatted + '\n' + 'Meta: ' + metaTagsCount + '\n' + 'Headings: ' + headingsCount + ' | ' + 'H1: ' + h1Count + ' | ' + 'H2: ' + h2Count + ' | ' + 'H3: ' + h3Count + ' | ' + 'H4: ' + h4Count + ' | ' + 'H5: ' + h5Count + ' | ' + 'H6: ' + h6Count + '\n' + 'Anchor: ' + anchorTagsCount + '\n' + 'CSS: ' + cssTagsCount + '\n' + 'JS: ' + jsTagsCount + '\n' + 'Canonical: ' + canonicalTagsCount + '\n' + 'img: ' + imgCount + ' | ' + 'picture: ' + pictureCount + ' | ' + 'video: ' + videoCount + '\n' + '<br>: ' + breakCount);
})();Visualise and count the internal and external links on a page.
Highlights every anchor element on the page.
π bookmarklets/links/highlight-links.js
javascript: (function() {
var anchorElements = document.querySelectorAll('a');
anchorElements.forEach(function(anchor) {
anchor.style.background = '#5e4899';
anchor.style.border = '1px solid #5e4899';
anchor.style.padding = '2px';
anchor.style.borderRadius = '5px';
anchor.style.color = '#adf0d6';
});
if (anchorElements.length > 0) {
alert('Links highlighted on the page.');
} else {
alert('No anchor elements found on this page.');
}
})();Counts all links and colour-codes each one, adding a data-link-index attribute.
π bookmarklets/links/count-links.js
javascript: (function() {
var anchorElements = document.querySelectorAll('a');
anchorElements.forEach(function(anchor, index) {
var backgroundColor = getRandomColor();
anchor.style.background = backgroundColor;
anchor.style.border = '2px solid #000';
anchor.style.padding = '2px';
anchor.setAttribute('data-link-index', index + 1);
});
var totalLinks = anchorElements.length;
if (totalLinks > 0) {
alert('Total links on the page: ' + totalLinks);
} else {
alert('No anchor elements found on this page.');
}
function getRandomColor() {
return '#' + Math.floor(Math.random() * 16777215).toString(16);
}
})();Inspect resource hints, script loading, fonts, and <head> ordering.
Shows separate alerts for the count and URLs of preconnect, dns-prefetch, preload, and prefetch links.
π bookmarklets/performance/resource-hints-detail.js
javascript:(function() {
function getLinkInfo(linkType) {
var linkElements = document.querySelectorAll('link[rel="' + linkType + '"]');
var linkCount = linkElements.length;
if (linkCount > 0) {
var linkInfo = Array.from(linkElements).map(function(link) {
return link.href;
}).join('\n');
alert(linkType + ' links (' + linkCount + '):\n' + linkInfo);
} else {
alert('No ' + linkType + ' links found on this page! π€');
}
}
getLinkInfo('preconnect');
getLinkInfo('dns-prefetch');
getLinkInfo('preload');
getLinkInfo('prefetch');
})();Lists every resource hint type β preload, prefetch, preconnect, dns-prefetch, prerender, modulepreload β and whether the page uses it.
π bookmarklets/performance/resource-hints-list.js
javascript:(function() {
const rels = [
"preload",
"prefetch",
"preconnect",
"dns-prefetch",
"preconnect dns-prefetch",
"prerender",
"modulepreload",
];
rels.forEach((element) => {
const linkElements = document.querySelectorAll(`link[rel="${element}"]`);
const dot = linkElements.length > 0 ? "π©" : "π₯";
console.log(`${dot} ${element}`);
linkElements.forEach((el) => console.log(el));
});
})();Source: Webperf-Snippets
Tables every <script src> showing whether it is async, defer, a module, and whether it is render-blocking.
π bookmarklets/performance/script-loading-types.js
javascript:(function() {
const scripts = document.querySelectorAll("script[src]");
const scriptsLoading = [...scripts].map((obj) => {
return {
src: obj.src,
async: obj.async,
defer: obj.defer,
module: obj.type === 'module',
"render blocking": obj.async || obj.defer || obj.type === 'module' ? "" : "π₯",
};
});
console.table(scriptsLoading);
})();Source: Webperf-Snippets
Lists the web fonts applied across the page (excluding the generic serif, sans-serif, and monospace families).
π bookmarklets/performance/web-fonts-in-use.js
javascript:(function() {
var allElements = document.querySelectorAll('*');
var webFonts = new Set();
allElements.forEach(function(element) {
var computedStyle = window.getComputedStyle(element);
var fontFamily = computedStyle.fontFamily;
fontFamily = fontFamily.replace(/['"]/g, '');
if (!(/^(serif|sans-serif|monospace)$/i.test(fontFamily))) {
webFonts.add(fontFamily.trim());
}
});
var webFontsCount = webFonts.size;
var webFontsList = Array.from(webFonts).join('\n');
alert('Number of Web Fonts: ' + webFontsCount + '\n\nWeb Font Names:\n' + webFontsList);
})();Lists fonts preloaded via resource hints, fonts loaded in the document, and fonts actually used above the fold.
π bookmarklets/performance/fonts-above-the-fold.js
javascript:(function() {
const linkElements = document.querySelectorAll(`link[rel="preload"]`);
const arrayLinks = Array.from(linkElements);
const preloadedFonts = arrayLinks.filter((link) => link.as === "font");
console.log(
"-> Fonts Preloaded via Resources Hints",
"font-weight: bold; font-size: 1.2em; color: lightcoral",
);
preloadedFonts.forEach((font) => console.log(`βΈ ${font.href}`));
console.log("");
const loadedFonts = [
...new Set(
Array.from(document.fonts.values())
.map((font) => font)
.filter((font) => font.status === "loaded")
.map((font) => `${font.family} - ${font.weight} - ${font.style}`),
),
];
console.log(
"-> Fonts and Weights Loaded in the Document",
"font-weight: bold; font-size: 1.2em; color: lightcoral",
);
loadedFonts.forEach((font) => console.log(`βΈ ${font}`));
console.log("");
const childrenSlector =
"body * > *:not(script):not(style):not(link):not(source)";
const aboveFoldElements = Array.from(
document.querySelectorAll(childrenSlector),
).filter((elm) => {
const rect = elm.getBoundingClientRect();
return (
rect.top >= 0 &&
rect.left >= 0 &&
rect.bottom <=
(window.innerHeight || document.documentElement.clientHeight) &&
rect.right <= (window.innerWidth || document.documentElement.clientWidth)
);
});
const usedFonts = Array.from(
new Set(
[...aboveFoldElements].map(
(e) =>
`${getComputedStyle(e).fontFamily} | ${
getComputedStyle(e).fontWeight
} | ${getComputedStyle(e).fontStyle}`,
),
),
);
console.log(
"-> Fonts and Weights Used Above the Fold",
"font-weight: bold; font-size: 1.2em; color: lightcoral",
);
usedFonts.forEach((font) => console.log(`βΈ ${font}`));
})();Source: Webperf-Snippets
Visualises the order of <head> elements and flags anything out of order, since <head> ordering can affect perceived performance.
π bookmarklets/performance/head-order-capo.js
β οΈ At ~30 KB, this script is too large to store reliably as a bookmark URL in Chrome. Run it as a DevTools Snippet instead (see Option 3), then read the output in the Console (β/Ctrl + Shift + J). The full source is in the linked file.
Source: capo.js by Rick Viscomi.
Read HTTP response headers and inspect cookies set by the page.
Displays the HTTP response headers for the current URL in an on-page modal.
π bookmarklets/technical/http-response-headers.js
javascript:(function() {
var modalContainer = document.createElement('div');
modalContainer.style.width = '1000px';
modalContainer.style.position = 'fixed';
modalContainer.style.top = '50%';
modalContainer.style.left = '50%';
modalContainer.style.transform = 'translate(-50%, -50%)';
modalContainer.style.backgroundColor = '#fff';
modalContainer.style.padding = '20px';
modalContainer.style.border = '1px solid #ccc';
modalContainer.style.zIndex = '9999';
modalContainer.style.direction = 'ltr';
modalContainer.style.textAlign = 'left';
modalContainer.style.overflowY = 'auto';
modalContainer.style.overflowX = 'auto';
var closeButton = document.createElement('button');
closeButton.innerHTML = 'Close';
closeButton.style.float = 'right';
closeButton.style.cursor = 'pointer';
closeButton.style.padding = '5px 10px';
closeButton.addEventListener('click', function() {
document.body.removeChild(modalContainer);
});
modalContainer.appendChild(closeButton);
var isGray = false;
var xhr = new XMLHttpRequest();
xhr.open('HEAD', window.location.href, true);
xhr.onreadystatechange = function() {
if (xhr.readyState == 4) {
var headers = xhr.getAllResponseHeaders().split('\n');
headers.forEach(function(header) {
var headerLine = header.trim();
var headerParagraph = document.createElement('p');
headerParagraph.style.wordWrap = 'break-word';
headerParagraph.style.marginBottom = '0px';
headerParagraph.style.paddingBottom = '10px';
headerParagraph.style.paddingTop = '10px';
headerParagraph.style.lineHeight = '10px';
headerParagraph.textContent = headerLine;
if (isGray) {
headerParagraph.style.backgroundColor = '#f5f5f5';
}
isGray = !isGray;
modalContainer.appendChild(headerParagraph);
});
document.body.appendChild(modalContainer);
}
};
xhr.send();
})();Shows all cookies set by the current page in a simple alert.
π bookmarklets/technical/cookies-alert.js
javascript:(function() {
function getCookies() {
var cookies = document.cookie.split(';');
var cookieInfo = cookies.map(function(cookie) {
var [name, value] = cookie.trim().split('=');
return name + ': ' + decodeURIComponent(value);
}).join('\n');
return cookieInfo || 'No cookies found.';
}
function showCookies() {
var cookieInfo = getCookies();
alert('Cookies:\n' + cookieInfo);
}
showCookies();
})();Shows all cookies set by the current page in a closable on-page modal.
π bookmarklets/technical/cookies-modal.js
javascript:(function() {
function createCookieModal() {
var modalContainer = document.createElement('div');
modalContainer.style.position = 'fixed';
modalContainer.style.top = '50%';
modalContainer.style.left = '50%';
modalContainer.style.transform = 'translate(-50%, -50%)';
modalContainer.style.backgroundColor = 'white';
modalContainer.style.padding = '20px';
modalContainer.style.border = '1px solid #222';
modalContainer.style.boxShadow = '0 0 10px rgba(0, 0, 0, 0.3)';
modalContainer.style.zIndex = '9999';
modalContainer.style.width = '1000px';
modalContainer.style.direction = 'ltr';
modalContainer.style.textAlign = 'left';
modalContainer.style.wordWrap = 'break-word';
var closeButton = document.createElement('button');
closeButton.textContent = 'Close';
closeButton.style.padding = '5px 10px';
closeButton.style.marginTop = '10px';
closeButton.style.cursor = 'pointer';
closeButton.style.right = '10px';
closeButton.style.top = '10px';
closeButton.style.position = 'fixed';
closeButton.addEventListener('click', function() {
modalContainer.style.display = 'none';
});
var cookies = document.cookie.split(';');
for (var i = 0; i < cookies.length; i++) {
var cookieLine = document.createElement('div');
cookieLine.textContent = cookies[i].trim();
modalContainer.appendChild(cookieLine);
}
modalContainer.appendChild(closeButton);
document.body.appendChild(modalContainer);
}
createCookieModal();
})();Inspect typography and colour contrast of selected text.
Select some text, then run the tool to see the font-family, size, weight, colour, and contrast ratio of the selection.
π bookmarklets/accessibility/text-font-and-contrast.js
javascript:(function() {
var selectedText = window.getSelection().toString();
if (selectedText) {
var element = window.getSelection().anchorNode.parentElement;
var fontFamily = window.getComputedStyle(element).fontFamily;
var fontSize = window.getComputedStyle(element).fontSize;
var fontWeight = window.getComputedStyle(element).fontWeight;
var color = window.getComputedStyle(element).color;
var backgroundColor = window.getComputedStyle(element).backgroundColor;
var contrastRatio = getContrastRatio(color, backgroundColor);
alert(
'Font Family: ' + fontFamily + '\n' +
'Font Size: ' + fontSize + '\n' +
'Font Weight: ' + fontWeight + '\n' +
'Color: ' + color + '\n' +
'Contrast Ratio: ' + contrastRatio
);
} else {
alert('Please select some text.');
}
function getContrastRatio(color1, color2) {
function getLuminance(color) {
var rgb = color.match(/\d+/g);
var r = rgb[0] / 255;
var g = rgb[1] / 255;
var b = rgb[2] / 255;
r = r <= 0.03928 ? r / 12.92 : Math.pow((r + 0.055) / 1.055, 2.4);
g = g <= 0.03928 ? g / 12.92 : Math.pow((g + 0.055) / 1.055, 2.4);
b = b <= 0.03928 ? b / 12.92 : Math.pow((b + 0.055) / 1.055, 2.4);
return 0.2126 * r + 0.7152 * g + 0.0722 * b;
}
var luminance1 = getLuminance(color1) + 0.05;
var luminance2 = getLuminance(color2) + 0.05;
var contrastRatio = luminance1 > luminance2 ? luminance1 / luminance2 : luminance2 / luminance1;
return contrastRatio.toFixed(2);
}
})();Inspect schema markup and how the page appears when shared on social platforms.
Extracts and pretty-prints all JSON-LD on the page in a modal, lists the schema @types found, and gives you a button to open Google's Rich Results Test.
π bookmarklets/structured-data/schema-viewer.js
javascript:(function() {
var blocks = document.querySelectorAll('script[type="application/ld+json"]');
if (!blocks.length) {
alert('No JSON-LD structured data found on this page. π€');
return;
}
var types = [];
var pretty = [];
blocks.forEach(function(b, i) {
var raw = b.textContent.trim();
try {
var data = JSON.parse(raw);
(function collect(node) {
if (Array.isArray(node)) node.forEach(collect);
else if (node && typeof node === 'object') {
if (node['@type']) types.push([].concat(node['@type']).join(', '));
Object.values(node).forEach(collect);
}
})(data);
pretty.push('/* Block ' + (i + 1) + ' */\n' + JSON.stringify(data, null, 2));
} catch (e) {
pretty.push('/* Block ' + (i + 1) + ' β invalid JSON */\n' + raw);
}
});
var overlay = document.createElement('div');
overlay.style.cssText = 'position:fixed;inset:0;background:rgba(0,0,0,.5);z-index:2147483647;display:flex;align-items:center;justify-content:center;';
var modal = document.createElement('div');
modal.style.cssText = 'background:#fff;color:#1c1b22;max-width:800px;width:90%;max-height:80vh;overflow:auto;border-radius:12px;padding:20px;font-family:monospace;text-align:left;direction:ltr;';
var head = document.createElement('div');
head.style.cssText = 'font-family:sans-serif;display:flex;justify-content:space-between;align-items:center;gap:10px;margin-bottom:12px;';
var title = document.createElement('strong');
title.textContent = blocks.length + ' JSON-LD block(s) β types: ' + (types.length ? Array.from(new Set(types)).join(', ') : 'none');
var btns = document.createElement('div');
var validate = document.createElement('button');
validate.textContent = 'Open Rich Results Test';
validate.style.cssText = 'cursor:pointer;margin-right:8px;padding:6px 10px;';
validate.onclick = function() {
window.open('https://search.google.com/test/rich-results?url=' + encodeURIComponent(window.location.href));
};
var close = document.createElement('button');
close.textContent = 'Close';
close.style.cssText = 'cursor:pointer;padding:6px 10px;';
close.onclick = function() {
overlay.remove();
};
btns.appendChild(validate);
btns.appendChild(close);
head.appendChild(title);
head.appendChild(btns);
var pre = document.createElement('pre');
pre.style.cssText = 'white-space:pre-wrap;word-break:break-word;margin:0;';
pre.textContent = pretty.join('\n\n');
modal.appendChild(head);
modal.appendChild(pre);
overlay.appendChild(modal);
overlay.addEventListener('click', function(e) {
if (e.target === overlay) overlay.remove();
});
document.body.appendChild(overlay);
})();Renders an Open Graph and X/Twitter card preview from the page's meta tags, flagging a missing og:image or twitter:card. The full source (with HTML escaping) is in the file:
π bookmarklets/structured-data/social-preview.js
Check whether a page can be indexed and quickly reach the files crawlers rely on.
Combines the meta robots directive, the X-Robots-Tag response header, and the canonical (self-referencing vs. pointing elsewhere) into a single "indexable: yes/no β why" verdict.
π bookmarklets/indexability/indexability-snapshot.js
javascript:(function() {
var metaRobots = (document.querySelector('meta[name="robots"]') || {}).content || '';
var canonical = (document.querySelector('link[rel="canonical"]') || {}).href || '';
var here = window.location.href.split('#')[0];
var selfCanon = canonical
? (canonical.split('#')[0] === here ? 'self-referencing β
' : 'points elsewhere β ' + canonical)
: 'none';
var xhr = new XMLHttpRequest();
xhr.open('HEAD', window.location.href, true);
xhr.onreadystatechange = function() {
if (xhr.readyState !== 4) return;
var xRobots = xhr.getResponseHeader('X-Robots-Tag') || '';
var noindex = /noindex/i.test(metaRobots) || /noindex/i.test(xRobots);
var verdict = noindex
? 'β NOT indexable (noindex directive found)'
: 'β
Indexable (no noindex directive found)';
if (!noindex && selfCanon.indexOf('points elsewhere') === 0) {
verdict = 'β οΈ Indexable, but the canonical points to another URL β this page may be consolidated into that one.';
}
alert('Indexability Snapshot\n\n' + verdict + '\n\n'
+ 'Meta robots: ' + (metaRobots || '(none)') + '\n'
+ 'X-Robots-Tag: ' + (xRobots || '(none)') + '\n'
+ 'Canonical: ' + selfCanon);
};
xhr.send();
})();Opens Google Search Console's URL Inspection tool for the exact current page.
Requires access to the matching property in Google Search Console.
π bookmarklets/indexability/gsc-url-inspection.js
javascript:(function() {
window.open('https://search.google.com/search-console/inspect?resource_id=' + encodeURIComponent(window.location.origin) + '&id=' + encodeURIComponent(window.location.href));
})();Opens /robots.txt for the current domain.
π bookmarklets/indexability/robots-txt.js
javascript:(function() {
window.open(window.location.origin + '/robots.txt');
})();Opens /sitemap.xml for the current domain.
π bookmarklets/indexability/sitemap-xml.js
javascript:(function() {
window.open(window.location.origin + '/sitemap.xml');
})();Opens /llms.txt for the current domain β the emerging standard for guiding AI crawlers.
π bookmarklets/indexability/llms-txt.js
javascript:(function() {
window.open(window.location.origin + '/llms.txt');
})();Opens the current URL's archive history on the Internet Archive.
π bookmarklets/indexability/wayback-machine.js
javascript:(function() {
window.open('https://web.archive.org/web/*/' + window.location.href);
})();Measure runtime performance directly in the page as it loads and as you interact.
Pins a live LCP / CLS / INP readout to the corner of the page, colour-rated (π’ / π‘ / π΄) against Google's thresholds and measured with PerformanceObserver. Interact with the page to register INP; click the bookmark again to dismiss.
π bookmarklets/performance-live/core-web-vitals-overlay.js
javascript:(function() {
if (window.__cwvOverlay) {
window.__cwvOverlay.remove();
window.__cwvOverlay = null;
return;
}
var box = document.createElement('div');
window.__cwvOverlay = box;
box.style.cssText = 'position:fixed;bottom:16px;right:16px;z-index:2147483647;background:#1c1b22;color:#fff;font:13px/1.4 monospace;padding:12px 14px;border-radius:10px;box-shadow:0 8px 24px rgba(0,0,0,.4);min-width:190px;';
var title = document.createElement('div');
title.textContent = 'Core Web Vitals (live)';
title.style.cssText = 'font-weight:bold;margin-bottom:6px;';
box.appendChild(title);
function row(label) {
var d = document.createElement('div');
d.textContent = label + ': measuringβ¦';
box.appendChild(d);
return d;
}
var lcpEl = row('LCP'), clsEl = row('CLS'), inpEl = row('INP');
var close = document.createElement('div');
close.textContent = 'β click to close';
close.style.cssText = 'margin-top:8px;cursor:pointer;opacity:.6;font-size:11px;';
close.onclick = function() {
box.remove();
window.__cwvOverlay = null;
};
box.appendChild(close);
document.body.appendChild(box);
function rate(v, good, poor) {
return v <= good ? 'π’' : (v <= poor ? 'π‘' : 'π΄');
}
try {
new PerformanceObserver(function(list) {
var es = list.getEntries();
var last = es[es.length - 1];
var v = Math.round(last.renderTime || last.loadTime || last.startTime);
lcpEl.textContent = 'LCP: ' + v + ' ms ' + rate(v, 2500, 4000);
}).observe({ type: 'largest-contentful-paint', buffered: true });
} catch (e) {
lcpEl.textContent = 'LCP: unsupported';
}
var cls = 0;
try {
new PerformanceObserver(function(list) {
list.getEntries().forEach(function(e) {
if (!e.hadRecentInput) cls += e.value;
});
clsEl.textContent = 'CLS: ' + cls.toFixed(3) + ' ' + rate(cls, 0.1, 0.25);
}).observe({ type: 'layout-shift', buffered: true });
} catch (e) {
clsEl.textContent = 'CLS: unsupported';
}
var maxInp = 0;
try {
new PerformanceObserver(function(list) {
list.getEntries().forEach(function(e) {
if (e.duration > maxInp) {
maxInp = e.duration;
inpEl.textContent = 'INP: ' + Math.round(maxInp) + ' ms ' + rate(maxInp, 200, 500);
}
});
}).observe({ type: 'event', durationThreshold: 16, buffered: true });
inpEl.textContent = 'INP: interact with the pageβ¦';
} catch (e) {
inpEl.textContent = 'INP: unsupported';
}
})();Finds render-blocking CSS and synchronous scripts in the <head> and lists them in a Console table, with an alert summary.
π bookmarklets/performance-live/render-blocking-resources.js
javascript:(function() {
var blocking = [];
document.querySelectorAll('head link[rel="stylesheet"]').forEach(function(l) {
var m = (l.media || '').toLowerCase();
if (!l.disabled && (m === '' || m === 'all' || m === 'screen')) {
blocking.push({ type: 'CSS', url: l.href });
}
});
document.querySelectorAll('head script[src]').forEach(function(s) {
if (!s.async && !s.defer && s.type !== 'module') {
blocking.push({ type: 'JS', url: s.src });
}
});
if (!blocking.length) {
alert('No obvious render-blocking resources found in <head>. π’');
return;
}
console.group('%cRender-blocking resources (' + blocking.length + ')', 'font-weight:bold;color:#c0392b;');
console.table(blocking);
console.groupEnd();
alert(blocking.length + ' render-blocking resource(s) in <head>.\nSee the Console (β/Ctrl + Shift + J) for the full list.');
})();If a bookmarklet isn't working:
- Check the code is complete. When copying manually, make sure the entire snippet (including the
javascript:prefix) was pasted into the bookmark's URL field. - Test it in the Console first. Open DevTools (β/Ctrl + Shift + J) and paste the code. If it runs without errors there, the problem is likely in how the bookmark was saved.
- Watch for site restrictions. Some pages enforce a strict Content Security Policy (CSP) that blocks inline scripts β bookmarklets may not run there.
- Cross-origin limits. Tools that read response headers or static HTML can be limited by CORS on some sites.
- Use the right output. Tools marked Console print to DevTools; tools marked Modal / On-page render directly on the page.
Contributions are welcome β new tools, fixes, and improvements. Please read CONTRIBUTING.md before opening a pull request, and note the Code of Conduct.
Nima Jafari β SEO consultant and educator.
This toolkit grew out of day-to-day technical SEO audits and teaching. If it saves you time, a β on the repository is always appreciated.
Released under the MIT License. You're free to use, modify, and share these tools β attribution is appreciated.
Several performance snippets are adapted from Webperf-Snippets and capo.js; credit remains with their original authors.