Skip to content

Instantly share code, notes, and snippets.

@cliffordp
Forked from acoyfellow/example.html
Last active December 31, 2025 11:33
Show Gist options
  • Select an option

  • Save cliffordp/335bee91f59a49a378cdfd44bd8f510f to your computer and use it in GitHub Desktop.

Select an option

Save cliffordp/335bee91f59a49a378cdfd44bd8f510f to your computer and use it in GitHub Desktop.
GHL: Add meaningful page titles based on heading content so they're not all the same
<script>
// This script: https://gist.github.com/cliffordp/335bee91f59a49a378cdfd44bd8f510f
// Based on https://www.facebook.com/groups/gohighlevel/posts/2734386397020878 from Jordan Coeyman
// Install this (without this comment block) via "Custom JS": https://app.gohighlevel.com/settings/company?tab=whitelabel
(function() {
'use strict';
const POLL_INTERVAL = 2000;
// selectors in priority order
const HEADING_SELECTORS = [
'.sidebar-v2-agency .active span',
'.sidebar-v2-location .active span',
'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 getSubaccountName() {
const subaccount = document.querySelector('.sidebar-v2-location .hl_location-text span.hl_switcher-loc-name');
return subaccount ? subaccount.textContent.trim() : '';
}
function getCleanText(el) {
if (!el) return null;
const clone = el.cloneNode(true);
const junkNodes = clone.querySelectorAll(
'button, svg, [class*="icon"], [class*="badge"], .sr-only'
);
junkNodes.forEach(function(n) {
if (n && n.parentNode) {
n.parentNode.removeChild(n);
}
});
let text = clone.textContent;
if (!text) return null;
text = text.trim().replace(/\s+/g, ' ');
if (!text || text.length < 2 || text.length > 100) return null;
return text;
}
function findBestHeading() {
for (let i = 0; i < HEADING_SELECTORS.length; i++) {
const selector = HEADING_SELECTORS[i];
try {
const el = document.querySelector(selector);
const text = getCleanText(el);
if (text) {
const sub = getSubaccountName();
return sub ? (text + ' | ' + sub) : text;
}
} catch (e) {
// ignore selector errors and continue
}
}
return null;
}
let updateScheduled = false;
function scheduleUpdateTitle() {
if (updateScheduled) return;
updateScheduled = true;
setTimeout(function() {
updateScheduled = false;
updateTitle();
}, 100);
}
function updateTitle() {
const heading = findBestHeading();
// CHANGED: do not append siteName, just use heading (or blank)
const newTitle = heading || '';
if (newTitle !== lastTitle) {
document.title = newTitle;
lastTitle = newTitle;
}
}
function checkForNavigation() {
if (location.href !== lastUrl) {
lastUrl = location.href;
scheduleUpdateTitle();
}
}
const observer = new MutationObserver(function() {
// Debounce updates so rapid DOM changes do not spam work
scheduleUpdateTitle();
});
if (document.body) {
observer.observe(document.body, {
childList: true,
subtree: true,
characterData: true
});
} else {
window.addEventListener('DOMContentLoaded', function() {
if (document.body) {
observer.observe(document.body, {
childList: true,
subtree: true,
characterData: true
});
}
updateTitle();
});
}
window.addEventListener('popstate', scheduleUpdateTitle);
window.addEventListener('hashchange', scheduleUpdateTitle);
setInterval(function() {
checkForNavigation();
scheduleUpdateTitle();
}, POLL_INTERVAL);
// Initial run
updateTitle();
})();
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment