Created
December 27, 2025 15:56
-
-
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <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