<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Daonware</title>
    <description>The latest articles on DEV Community by Daonware (@daonwareit).</description>
    <link>https://dev.to/daonwareit</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4002395%2Fb3f6d671-0f56-403e-8a87-58a3d10f0e31.png</url>
      <title>DEV Community: Daonware</title>
      <link>https://dev.to/daonwareit</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/daonwareit"/>
    <language>en</language>
    <item>
      <title>I Built a Browser Extension to Bring Back the Like/Dislike Badge in YouTube Studio</title>
      <dc:creator>Daonware</dc:creator>
      <pubDate>Thu, 25 Jun 2026 12:55:37 +0000</pubDate>
      <link>https://dev.to/daonwareit/i-built-a-browser-extension-to-bring-back-the-likedislike-badge-in-youtube-studio-cn6</link>
      <guid>https://dev.to/daonwareit/i-built-a-browser-extension-to-bring-back-the-likedislike-badge-in-youtube-studio-cn6</guid>
      <description>&lt;p&gt;YouTube Studio used to show a small like/dislike badge right next to each video in the content tab. At some point it just... disappeared. No announcement, no reason — it was simply gone.&lt;/p&gt;

&lt;p&gt;As someone who likes to keep an eye on how videos are performing at a glance, this was annoying. Opening each video individually just to check the ratio felt like a massive step backwards.&lt;/p&gt;

&lt;p&gt;So I did what developers do: I built a fix.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;If you manage a YouTube channel, you know the content tab is your command center. At a glance you can see views, comments, watch time — but &lt;strong&gt;no like/dislike ratio&lt;/strong&gt;. YouTube quietly stopped showing it.&lt;/p&gt;

&lt;p&gt;The numbers aren't gone though. YouTube Studio still loads them internally when you open the page. They're just... not displayed anymore.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Idea: Intercept, Don't Request
&lt;/h2&gt;

&lt;p&gt;My first instinct was to use the YouTube Data API. But that comes with OAuth flows, quota limits, and a setup process that's anything but seamless.&lt;/p&gt;

&lt;p&gt;Then I noticed something while digging through DevTools: YouTube Studio's own frontend was already fetching the like counts via internal &lt;code&gt;youtubei&lt;/code&gt; endpoints — it just wasn't rendering them.&lt;/p&gt;

&lt;p&gt;That changed everything. Instead of making new API calls, I could simply &lt;strong&gt;listen to what the page was already loading&lt;/strong&gt; and inject the badge myself.&lt;/p&gt;




&lt;h2&gt;
  
  
  How It Works
&lt;/h2&gt;

&lt;p&gt;The extension has two core scripts:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;src/inject.js&lt;/code&gt;&lt;/strong&gt; — Intercepts the internal network responses from &lt;code&gt;studio.youtube.com&lt;/code&gt; by wrapping the native &lt;code&gt;XMLHttpRequest&lt;/code&gt;. When a response contains video metadata (detected via known field names like &lt;code&gt;likeCount&lt;/code&gt; and &lt;code&gt;videoId&lt;/code&gt;), it extracts the data and broadcasts it via a custom DOM event.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Simplified version of the interception logic&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;originalOpen&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;XMLHttpRequest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;prototype&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;open&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;XMLHttpRequest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;prototype&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;open&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(...&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;load&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;json&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;responseText&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;videos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;extractVideos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// walk the response tree&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;videos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dispatchEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;CustomEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ytsr-data&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;detail&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;videos&lt;/span&gt; &lt;span class="p"&gt;}));&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;originalOpen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;code&gt;src/content.js&lt;/code&gt;&lt;/strong&gt; — Listens for those custom events and injects the badge into the correct DOM row for each video. It uses a &lt;code&gt;MutationObserver&lt;/code&gt; to handle YouTube's dynamic rendering.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ytsr-data&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;detail&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;videos&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;video&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;videos&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;row&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;findRowByVideoId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;video&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nf"&gt;injectBadge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;video&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;likes&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The result: a &lt;code&gt;👍 92.4%&lt;/code&gt; badge appears directly in the content tab, with exact numbers on hover.&lt;/p&gt;




&lt;h2&gt;
  
  
  Privacy First — By Design
&lt;/h2&gt;

&lt;p&gt;This was a non-negotiable constraint from the start:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ Runs &lt;strong&gt;only&lt;/strong&gt; on &lt;code&gt;studio.youtube.com&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;✅ Makes &lt;strong&gt;zero additional network requests&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;✅ Sends &lt;strong&gt;no data&lt;/strong&gt; to any third-party server&lt;/li&gt;
&lt;li&gt;✅ Only reads data from &lt;strong&gt;your own logged-in channel&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;✅ Nothing is stored beyond your local browser session&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The extension is essentially a read-only lens on data that was already traveling through your browser.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Tricky Parts
&lt;/h2&gt;

&lt;h3&gt;
  
  
  YouTube Changes Without Warning
&lt;/h3&gt;

&lt;p&gt;Internal endpoints aren't documented. YouTube can rename field keys at any time — and they do. To handle this, I built a &lt;strong&gt;debug mode&lt;/strong&gt; directly into the popup. When enabled, it logs every intercepted payload to the console under a &lt;code&gt;[YTSR]&lt;/code&gt; prefix, making it easy to spot which field names changed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Dislikes Are Not There (Yet)
&lt;/h3&gt;

&lt;p&gt;Here's the honest limitation: &lt;strong&gt;YouTube does not include dislike counts in the Studio content endpoint.&lt;/strong&gt; The extension currently shows like counts only. The "ratio" in the name reflects the roadmap — once I integrate the YouTube Analytics API (which does expose dislikes), the badge will become a true ratio. That requires OAuth per user though, so the UX tradeoff needs careful thought.&lt;/p&gt;

&lt;h3&gt;
  
  
  MV3 Restrictions
&lt;/h3&gt;

&lt;p&gt;Manifest V3 removed &lt;code&gt;webRequest&lt;/code&gt; blocking, which is commonly used for response interception. The workaround here is wrapping &lt;code&gt;XMLHttpRequest&lt;/code&gt; from an injected script — which works, but requires careful coordination between the injected world and the content script world via DOM events.&lt;/p&gt;




&lt;h2&gt;
  
  
  Tech Stack
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Extension API&lt;/td&gt;
&lt;td&gt;Manifest V3 (Chrome, Edge, Brave, Firefox)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Core Logic&lt;/td&gt;
&lt;td&gt;Vanilla JavaScript&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Build&lt;/td&gt;
&lt;td&gt;None — ships as unpacked source&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dev Environment&lt;/td&gt;
&lt;td&gt;VS Code&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Version Control&lt;/td&gt;
&lt;td&gt;GitHub&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Deliberately no framework, no bundler, no dependencies. The whole extension is a handful of files.&lt;/p&gt;




&lt;h2&gt;
  
  
  What I Learned
&lt;/h2&gt;

&lt;p&gt;Building a browser extension that intercepts live page data taught me a lot about how modern web apps load and render dynamic content. The gap between "the data exists" and "the data is visible" is often just a missing render step — and that's a surprisingly powerful place to work.&lt;/p&gt;

&lt;p&gt;It also reinforced something I keep coming back to: &lt;strong&gt;the best tools solve your own problem first.&lt;/strong&gt; I built this because I genuinely missed that badge. Every decision — no external requests, no OAuth, debug mode built-in — came from thinking about what I'd want as a user.&lt;/p&gt;




&lt;h2&gt;
  
  
  Try It Yourself
&lt;/h2&gt;

&lt;p&gt;The extension is open source and ready to install locally:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Clone the repo: &lt;code&gt;git clone https://github.com/daonware-it/yt-studio-ratio&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Open &lt;code&gt;chrome://extensions&lt;/code&gt; and enable Developer Mode&lt;/li&gt;
&lt;li&gt;Click "Load unpacked" and select the folder&lt;/li&gt;
&lt;li&gt;Head to YouTube Studio → Content tab&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;👉 &lt;strong&gt;&lt;a href="https://github.com/daonware-it/yt-studio-ratio" rel="noopener noreferrer"&gt;GitHub — daonware-it/yt-studio-ratio&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;
👉 &lt;strong&gt;&lt;a href="https://chromewebstore.google.com/detail/yt-studio-likedislike-rat/glojdhijcgeieoikihijfmdjfilhemcm?authuser=0&amp;amp;hl=de" rel="noopener noreferrer"&gt;Google Chrome Extension — YT Studio Like/Dislike Ratio&lt;br&gt;
&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If the badge doesn't appear, flip on Debug Mode in the popup and open the console — you'll see exactly what's happening. And if you run into issues or have ideas, open an issue or drop a comment below. I'd love to hear how it works for you.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>opensource</category>
      <category>html</category>
    </item>
  </channel>
</rss>
