Skip to content

Instantly share code, notes, and snippets.

@arafathusayn
Last active May 31, 2026 15:35
Show Gist options
  • Select an option

  • Save arafathusayn/101a211b3d8122876d70cf1af04ae81d to your computer and use it in GitHub Desktop.

Select an option

Save arafathusayn/101a211b3d8122876d70cf1af04ae81d to your computer and use it in GitHub Desktop.
Remove Tawk.to Branding (2026)
/**
* Remove Tawk.to Branding
*
* What it hides:
* - Footer branding links (tawk.to, utm_source=tawk-messenger)
* - "Add Chat to your website" link
* - Popout button/icon
* - Branding container classes
*
* Key design decisions:
* - CSS injection over DOM removal (less disruptive, survives re-renders)
* - MutationObserver over setInterval (reacts to changes, no wasted cycles)
* - Debounced re-scans (Tawk rebuilds its widget DOM sometimes)
* - iframe `load` listener stays active (catches SPA navigations)
* - JS fallback for popout buttons (`:has()` unsupported in older browsers)
*/
;(function removeTawkBranding() {
"use strict";
// ── Config ────────────────────────────────────────────────────────────
var STYLE_ID = "hide-tawk-branding";
// NOTE: No `:contains()` (jQuery-only, invalid in browsers).
// No `:has()` in CSS (Firefox < 121, older Safari/Chrome).
// Popout buttons handled via JS fallback below.
var BRANDING_CSS = [
"a[href*='tawk.to'] { display: none !important; }",
"a[href*='utm_source=tawk-messenger'] { display: none !important; }",
"a[title*='Add Chat to your website'] { display: none !important; }",
".tawk-branding { display: none !important; }",
"[class*='tawk-branding'] { display: none !important; }",
".tawk-padding-small { display: none !important; }",
".tawk-icon-popout { display: none !important; }",
].join("\n");
var RETRY_DELAY_MS = 1500;
// ── State ─────────────────────────────────────────────────────────────
var processedIframes = new WeakSet();
var pendingRetries = new Map();
var debounceTimer = null;
// ── Core ──────────────────────────────────────────────────────────────
/**
* Inject a <style> into a document. Returns true on success.
*/
function injectStyle(doc) {
try {
if (!doc || !doc.createElement) return false;
if (doc.getElementById(STYLE_ID)) return true;
var style = doc.createElement("style");
style.id = STYLE_ID;
style.textContent = BRANDING_CSS;
var target = doc.head || doc.documentElement;
if (target) {
target.appendChild(style);
return true;
}
} catch (_) {
// Cross-origin — expected.
}
return false;
}
/**
* JS fallback: hide popout buttons for browsers without CSS `:has()`.
*/
function hidePopoutButtons(doc) {
try {
if (!doc || !doc.querySelectorAll) return;
var icons = doc.querySelectorAll(".tawk-icon-popout");
for (var i = 0; i < icons.length; i++) {
var btn = icons[i].closest("button");
if (btn) btn.style.setProperty("display", "none", "important");
}
} catch (_) {}
}
/**
* Inject styles into an iframe. Retries once if contentDocument isn't ready.
*/
function injectIntoIframe(iframe) {
try {
var doc = iframe.contentDocument;
if (!doc) {
// Schedule a single retry if iframe not ready yet
if (!pendingRetries.has(iframe)) {
var timeout = setTimeout(function () {
pendingRetries.delete(iframe);
injectIntoIframe(iframe);
}, RETRY_DELAY_MS);
pendingRetries.set(iframe, timeout);
}
return;
}
injectStyle(doc);
hidePopoutButtons(doc);
} catch (_) {
// Cross-origin — expected.
}
}
/**
* Process a chat iframe: mark as processed, inject now, re-inject on reload.
*/
function handleIframe(iframe) {
if (processedIframes.has(iframe)) return;
var title = (iframe.title || "").toLowerCase();
if (title.indexOf("chat") === -1) return;
processedIframes.add(iframe);
// Inject immediately (may already be loaded)
injectIntoIframe(iframe);
// Re-inject on subsequent loads (SPA navigation, widget rebuild).
// Intentionally NOT `{ once: true }` — Tawk can reload iframe content.
iframe.addEventListener("load", function () {
injectIntoIframe(iframe);
});
}
/**
* Scan a root document for all chat iframes.
*/
function scanIframes(root) {
try {
var iframes = (root || document).querySelectorAll("iframe");
for (var i = 0; i < iframes.length; i++) {
handleIframe(iframes[i]);
}
} catch (_) {}
}
// ── Observer ──────────────────────────────────────────────────────────
function onMutations(mutations) {
var foundNewIframe = false;
for (var i = 0; i < mutations.length; i++) {
var added = mutations[i].addedNodes;
for (var j = 0; j < added.length; j++) {
var node = added[j];
if (node.nodeName === "IFRAME") {
handleIframe(node);
foundNewIframe = true;
} else if (node.nodeType === 1 && node.querySelectorAll) {
var nested = node.querySelectorAll("iframe");
if (nested.length) {
for (var k = 0; k < nested.length; k++) {
handleIframe(nested[k]);
}
foundNewIframe = true;
}
}
}
}
// Debounced re-scan: Tawk sometimes rebuilds its widget DOM entirely,
// so existing iframes may get new content without a new IFRAME node.
if (foundNewIframe && !debounceTimer) {
debounceTimer = setTimeout(function () {
debounceTimer = null;
scanIframes();
hidePopoutButtons(document);
}, 500);
}
}
// ── Bootstrap ─────────────────────────────────────────────────────────
function start() {
// Main document branding (outside iframes)
injectStyle(document);
hidePopoutButtons(document);
// Initial iframe scan
scanIframes();
// Watch for dynamically added iframes
if (typeof MutationObserver !== "undefined" && document.body) {
var observer = new MutationObserver(onMutations);
observer.observe(document.body, { childList: true, subtree: true });
// Cleanup
window.addEventListener("beforeunload", function () {
observer.disconnect();
pendingRetries.forEach(clearTimeout);
pendingRetries.clear();
if (debounceTimer) {
clearTimeout(debounceTimer);
debounceTimer = null;
}
});
}
}
// Guard: script may be in <head> before document.body exists
if (document.body) {
start();
} else {
document.addEventListener("DOMContentLoaded", start);
}
})();
@hazelcorp2011-cmd

Copy link
Copy Markdown

Anyone can help me for removing tawk chat branding with plan JS Code.

@smlaticc

Copy link
Copy Markdown

Removed branding and also 2 items from dropdown menu.
Tested on tawk.to plugin for wordpress.
Works on mobile also.
Here:

var removeBranding = function () {

  var iframes = document.querySelectorAll("iframe");

  iframes.forEach(function (ifr, idx) {

    try {
      var doc = ifr.contentDocument || (ifr.contentWindow && ifr.contentWindow.document);      
      if (!doc) return;

      var btn = doc.querySelector('a[class*="tawk-button-small"]');
      if (btn) {
        btn.remove();
      }

      var popoutIcon = doc.querySelector("i.tawk-icon.tawk-icon-popout");
      if (popoutIcon) {
        var popoutButton = popoutIcon.closest("button");
        if (popoutButton) {
          popoutButton.remove();
        }
      }

      var byHref = doc.querySelector('a[href*="tawk.to/?utm_source=tawk-messenger"], a[href*="tawk.to/?utm_"]');
      if (byHref) {
        console.log("Removed by href:", byHref);
        byHref.remove();
        return;
      }

      var svgBranding = doc.querySelector('a svg[viewBox="0 0 107 15"]');
      if (svgBranding) {
        var brandingLink = svgBranding.closest("a");
        if (brandingLink) {
          console.log("Removed SVG branding:", brandingLink);
          brandingLink.remove();
          return;
        }
      }

      var nodes = Array.from(doc.querySelectorAll("a, div, span"));
      var poweredBy = nodes.find(function (el) {
        return normalizeText(el.textContent).includes("powered by tawk.to");
      });

      if (poweredBy) {
        console.log("Removed powered by text:", poweredBy);
        var a = poweredBy.tagName === "A" ? poweredBy : poweredBy.querySelector("a");
        (a || poweredBy).remove();
      }
    } 
    catch (e) {

    }
  });
};
setInterval(removeBranding, 250);

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment