Make YouTube display the names of commenters instead of their handles.

August 18, 2026 ยท View on GitHub

// ==UserScript== // @name YouTube Commenter Names // @version 1.13.0 // @description Make YouTube display the names of commenters instead of their handles. // @author Lumynous // @license MIT // @match https://www.youtube.com/* // @match https://music.youtube.com/* // @match https://studio.youtube.com/* // @exclude https://www.youtube.com/persist_identity // @exclude https://studio.youtube.com/persist_identity // @exclude https://studio.youtube.com/ytscframe // @exclude https://www.youtube.com/embed/* // @grant none // @downloadURL https://gist.github.com/lumynou5/74bcbab54cd9d8fcd3c873fffbac5d3d/raw/youtube-commenter-names.user.js // @updateURL https://gist.github.com/lumynou5/74bcbab54cd9d8fcd3c873fffbac5d3d/raw/~meta // ==/UserScript==

"use strict";

const watchElm = (function () { const elmObserver = new MutationObserver(elmObserverCallback); elmObserver.observe(document, {childList: true, subtree: true}); const callbacks = []; // Array is faster since we don't remove.

function elmObserverCallback(mutations) {
	for (const mutation of mutations) {
		for (const node of mutation.addedNodes) {
			if (node.nodeType !== Node.ELEMENT_NODE)
				continue;
			for (const {selector, callback} of callbacks) {
				if (node.matches(selector))
					callback(node);
				for (const elm of node.querySelectorAll(selector))
					callback(elm);
			}
		}
	}
}

function elmCallback(observer, action, elm) {
	observer.observe(elm, {attributeFilter: ["id", "href", "whole-message-clickable"]});
	action(elm);
}

return (selector, action) => {
	const observer = new MutationObserver((mutations) => {
		for (const mutation of mutations)
			action(mutation.target);
	});
	const callback = elmCallback.bind(null, observer, action);
	for (const elm of document.querySelectorAll(selector))
		callback(elm);
	callbacks.push({selector, callback});
};

})();

async function fetchInternalApi(endpoint, body) { const response = await fetch( https://www.youtube.com/youtubei/v1/${endpoint}?key=AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8&prettyPrint=false, { method: "POST", body: JSON.stringify({ context: {client: {clientName: "WEB", clientVersion: "2.20240411.01.00"}}, ...body, }), }, ); return await response.json(); }

function cacheFunctionDecorator(fn) { const cache = new Map(); const decoratedFn = (arg) => { let res = cache.get(arg); if (res === void 0) { res = fn(arg); cache.set(arg, res); } return res; }; decoratedFn.cache = cache; return decoratedFn; }

const getChannelId = cacheFunctionDecorator(async (handle) => { let json = await fetchInternalApi("navigation/resolve_url", { url: https://www.youtube.com/${handle}, }); if (!json.endpoint.browseEndpoint) { // Workaround: Some channels such as @rayduenglish behave strange. Normally GETing // channel pages result 303 and redirect to /rayduenglish for example; the internal // API responses similarly, the workaround is to resolve twice. However, some are // impossible to resolve correctly; for example, requesting /@Konata redirected to // /user/Konata, and /user/Konata leads 404. This is probably a bug of YouTube. json = await fetchInternalApi("navigation/resolve_url", json.endpoint.urlEndpoint); } return json.endpoint.browseEndpoint.browseId; });

const getChannelName = cacheFunctionDecorator(async (id) => { const json = await fetchInternalApi("browse", {browseId: id}); return json.metadata.channelMetadataRenderer.title; });

function replaceText(elm, text) { elm.firstChild.textContent = text; }

switch (true) { case location.pathname === "/live_chat": watchElm("yt-live-chat-participant-renderer", async (elm) => { let name; if (elm.data.authorBadges?.at(-1).liveChatAuthorBadgeRenderer.icon?.iconType === "OWNER") name = top.__lmn?.ownerName; else name = await getChannelName(elm.data.authorExternalChannelId); name && replaceText(elm.querySelector("#author-name"), name); }); watchElm("yt-live-chat-banner-redirect-renderer", async (elm) => { const handle = elm.data.bannerMessage.runs.find((x) => x.bold).text; const name = await getChannelName(await getChannelId(handle)); replaceText(elm.querySelector("#banner-text .bold"), name); }); // fallthrough

case location.pathname === "/live_chat_replay":
	watchElm("ytd-sponsorships-live-chat-gift-redemption-banner-renderer", async (elm) => {
		let idx = elm.data.headerText.runs.findIndex((x) => x.text[0] === "@");
		let name = await getChannelName(await getChannelId(elm.data.headerText.runs[idx].text));
		replaceText(elm.querySelector(`#header-text span:nth-child(${idx + 1})`), name);
		idx = elm.data.messageText.runs.findIndex((x) => x.text[0] === "@");
		if ((name = top.__lmn?.ownerName))
			replaceText(elm.querySelector(`#message-text span:nth-child(${idx + 1})`), name);
	});
	watchElm("yt-live-chat-ticker-paid-message-item-renderer", async (elm) => {
		const name = await getChannelName(elm.data.authorExternalChannelId);
		replaceText(elm.querySelector("span"), name);
	});
	watchElm(`yt-live-chat-text-message-renderer,
	          yt-live-chat-paid-message-renderer,
	          yt-live-chat-paid-sticker-renderer,
	          yt-live-chat-membership-item-renderer,
	          ytd-sponsorships-live-chat-gift-purchase-announcement-renderer,
	          ytd-sponsorships-live-chat-gift-redemption-announcement-renderer`, async (elm) => {
		let name = await getChannelName(elm.data.authorExternalChannelId);
		replaceText(elm.querySelector("#author-name"), name);
		// If the message is a reply.
		if (elm.data.beforeContentButtons?.at(-1).buttonViewModel.iconName !== "MESSAGE")
			return;
		name = await getChannelName(await getChannelId(elm.data.beforeContentButtons.at(-1).buttonViewModel.title));
		replaceText(elm.querySelector("#before-content-buttons > :last-child .ytSpecButtonShapeNextButtonTextContent"), name);
	});
	watchElm("yt-gift-message-view-model", async (elm) => {
		const name = await getChannelName(await getChannelId(elm.data.authorName.content));
		replaceText(elm.querySelector("#author-name-v2").firstElementChild.firstElementChild, name);
	});
	// Get handle only.
	watchElm("yt-live-chat-paid-message-renderer", (elm) => {
		const handle = elm.data.authorName.simpleText;
		if (!getChannelId.cache.has(handle))
			getChannelId.cache.set(handle, Promise.resolve(elm.data.authorExternalChannelId));
	});
	watchElm("ytd-sponsorships-live-chat-gift-purchase-announcement-renderer", (elm) => {
		const handle = elm.data.header.liveChatSponsorshipsHeaderRenderer.authorName.simpleText;
		if (!getChannelId.cache.has(handle))
			getChannelId.cache.set(handle, Promise.resolve(elm.data.authorExternalChannelId));
	});
	// Replace gifter name only.
	watchElm("ytd-sponsorships-live-chat-gift-redemption-announcement-renderer", async (elm) => {
		const gifter = elm.data.message.runs.find((x) => x.bold).text;
		const name = await getChannelName(await getChannelId(gifter));
		replaceText(elm.querySelector("#message .bold"), name);
	});
	break;

case location.hostname === "www.youtube.com":
	window.__lmn = {};
	// Get video owner names without requesting.  Video owners likely have comments on their videos.
	document.addEventListener("yt-page-data-updated", (ev) => {
		if (ev.detail.pageType !== "watch")
			return;
		const videoOwner = ev.target.get("data").response.contents.twoColumnWatchNextResults
			.results.results.contents.find((x) => x.videoSecondaryInfoRenderer)
			.videoSecondaryInfoRenderer.owner.videoOwnerRenderer;
		if (videoOwner.title) {
			const id = videoOwner.title.runs[0].navigationEndpoint.browseEndpoint.browseId;
			const name = videoOwner.title.runs[0].text;
			if (!getChannelName.cache.has(id))
				getChannelName.cache.set(id, Promise.resolve(name));
			window.__lmn.ownerName = name;
		} else {
			// Collaborated videos.
			videoOwner.navigationEndpoint.showDialogCommand.panelLoadingStrategy.inlineContent
				.dialogViewModel.customContent.listViewModel.listItems
				.forEach(({listItemViewModel: {title}}) => {
					const id = title.commandRuns[0].onTap.innertubeCommand.browseEndpoint.browseId;
					if (!getChannelName.cache.has(id))
						getChannelName.cache.set(id, Promise.resolve(title.content));
				});
		}
	});
	// Mentions in titles.
	watchElm("#title.ytd-watch-metadata a.yt-simple-endpoint", async (elm) => {
		if (elm.pathname[1] !== "@")
			return;
		const name = await getChannelName(elm.data.browseEndpoint.browseId);
		replaceText(elm, name);
	});
	// fallthrough

case location.hostname === "music.youtube.com":
	// Commenters.
	watchElm("#author-text.ytd-comment-view-model", async (elm) => {
		const name = await getChannelName(elm.data.browseEndpoint.browseId);
		replaceText(elm.firstElementChild, name);
	});
	watchElm("#name.ytd-author-comment-badge-renderer", async (elm) => {
		const name = await getChannelName(elm.data.browseEndpoint.browseId);
		replaceText(elm.querySelector("#text"), name);
	});
	// Mentions in comments.
	watchElm("#content-text.ytd-comment-view-model a", async (elm) => {
		// Skip non-mentions.  URLs with protocol begin with `http` only.
		// Channels begin with `/channel/`.
		if (elm.attributes.href.value[1] !== "c")
			return;
		const name = await getChannelName(elm.href.slice(elm.href.lastIndexOf("/") + 1));
		replaceText(elm, `\xA0${name}\xA0`);
	});
	break;

default: {
	const observer = new IntersectionObserver(async (entries) => {
		for (const {isIntersecting, target} of entries) {
			if (!isIntersecting)
				continue;
			const name = await getChannelName(await getChannelId(target.data.authorText.simpleText));
			replaceText(target.querySelector(".author-text"), name);
		}
	});
	watchElm("ytcp-comment", (elm) => observer.observe(elm));
	watchElm("ytcp-author-comment-badge", async (elm) => {
		const name = await getChannelName(await getChannelId(elm.data.authorText.simpleText));
		replaceText(elm.firstElementChild.firstElementChild, name);
	});
}

}