Show live word count in the Overleaf PDF toolbar
// ==UserScript==
// @name Overleaf Toolbar Word Count
// @namespace parnavh.dev
// @version 2026-08-04
// @description Show live word count in the Overleaf PDF toolbar
// @match https://www.overleaf.com/project/*
// @grant none
// @author parnavh
// @icon https://www.google.com/s2/favicons?sz=64&domain=overleaf.com
// @license MIT
// ==/UserScript==
(function () {
'use strict';
let counter;
async function updateWordCount() {
try {
const res = await fetch(location.pathname + "/wordcount", {
credentials: "same-origin",
cache: "no-store"
});
if (!res.ok) return;
const { texcount } = await res.json();
counter.textContent = `Words: ${texcount.textWords.toLocaleString()}`;
} catch (e) {
console.error("Word count failed", e);
}
}
function install() {
if (document.getElementById("tm-word-count"))
return false;
const toolbar = document.querySelector(".toolbar-pdf-left");
if (!toolbar)
return false;
counter = document.createElement("span");
counter.id = "tm-word-count";
counter.textContent = "Words: ...";
counter.style.cssText = `
font-size:14px;
`;
toolbar.appendChild(counter);
updateWordCount();
const compileBtn = document.querySelector(".compile-button");
if (compileBtn) {
const compileObserver = new MutationObserver(() => {
if (compileBtn.dataset.olLoading === "false") {
updateWordCount();
}
});
compileObserver.observe(compileBtn, {
attributes: true,
attributeFilter: ["data-ol-loading"]
});
}
return true;
}
// Wait until Overleaf has rendered
const observer = new MutationObserver(() => {
if (install()) {
observer.disconnect();
}
});
observer.observe(document.body, {
childList: true,
subtree: true
});
})();