Skip to content

Instantly share code, notes, and snippets.

@acoyfellow
Created December 27, 2025 15:56
Show Gist options
  • Select an option

  • Save acoyfellow/7b57483cbcd599d37aa60436cf56f1b5 to your computer and use it in GitHub Desktop.

Select an option

Save acoyfellow/7b57483cbcd599d37aa60436cf56f1b5 to your computer and use it in GitHub Desktop.
Adds meaningful page titles based on heading content for sites with bad/missing titles
<script>
// install this via "Custom JS":
// https://app.gohighlevel.com/settings/company?tab=whitelabel
// ==UserScript==
// @name Auto Title Fixer
// @namespace sendgrowth
// @version 1.0
// @description Adds meaningful page titles based on heading content for sites with bad/missing titles
// @match *://*.example.com/*
// @grant none
// @run-at document-idle
// ==/UserScript==
(function() {
'use strict';
const POLL_INTERVAL = 2000;
// selectors in priority order
const HEADING_SELECTORS = [
'h1',
'[role="heading"][aria-level="1"]',
'.page-title',
'.header-title',
'[data-testid="page-title"]',
'main h1',
'main h2',
'.content-header h1',
'.content-header h2',
'h2',
'[class*="title" i]:not(button):not(a)',
'[class*="heading" i]:not(button):not(a)',
];
let lastTitle = null;
let lastUrl = location.href;
const siteName = location.hostname.split('.').slice(-2, -1)[0] || location.hostname;
function getCleanText(el) {
if (!el) return null;
const clone = el.cloneNode(true);
clone.querySelectorAll('button, svg, [class*="icon"], [class*="badge"], .sr-only').forEach(n => n.remove());
const text = clone.textContent?.trim().replace(/\s+/g, ' ');
if (!text || text.length < 2 || text.length > 100) return null;
return text;
}
function findBestHeading() {
for (const selector of HEADING_SELECTORS) {
try {
const el = document.querySelector(selector);
const text = getCleanText(el);
if (text) return text;
} catch (e) {}
}
return null;
}
function updateTitle() {
const heading = findBestHeading();
const newTitle = heading ? `${heading} | ${siteName}` : siteName;
if (newTitle !== lastTitle) {
document.title = newTitle;
lastTitle = newTitle;
}
}
function checkForNavigation() {
if (location.href !== lastUrl) {
lastUrl = location.href;
setTimeout(updateTitle, 100);
}
}
const observer = new MutationObserver(() => updateTitle());
observer.observe(document.body, {
childList: true,
subtree: true,
characterData: true
});
window.addEventListener('popstate', () => setTimeout(updateTitle, 100));
window.addEventListener('hashchange', () => setTimeout(updateTitle, 100));
setInterval(() => {
checkForNavigation();
updateTitle();
}, POLL_INTERVAL);
updateTitle();
})();
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment