<?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: tarek gritli</title>
    <description>The latest articles on DEV Community by tarek gritli (@tarek_gritli_49d248cbd642).</description>
    <link>https://dev.to/tarek_gritli_49d248cbd642</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3406567%2F48ebfc81-67cd-44e6-9185-9179c004cce7.png</url>
      <title>DEV Community: tarek gritli</title>
      <link>https://dev.to/tarek_gritli_49d248cbd642</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/tarek_gritli_49d248cbd642"/>
    <language>en</language>
    <item>
      <title>🧩 Enhancing Cloudflare R2 with a Custom Chrome Extension for Object Sorting</title>
      <dc:creator>tarek gritli</dc:creator>
      <pubDate>Fri, 01 Aug 2025 19:33:58 +0000</pubDate>
      <link>https://dev.to/tarek_gritli_49d248cbd642/enhancing-cloudflare-r2-with-a-custom-chrome-extension-for-object-sorting-55hl</link>
      <guid>https://dev.to/tarek_gritli_49d248cbd642/enhancing-cloudflare-r2-with-a-custom-chrome-extension-for-object-sorting-55hl</guid>
      <description>&lt;p&gt;&lt;em&gt;Ever tried finding a specific file among hundreds in your Cloudflare R2 bucket? Yeah, me too. So I built a solution.&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;Cloudflare R2's dashboard is great, but it's missing one crucial feature: &lt;strong&gt;sorting&lt;/strong&gt;. When you have dozens or hundreds of files, you can't:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Find your largest files to optimize storage costs&lt;/li&gt;
&lt;li&gt;Locate recently uploaded files
&lt;/li&gt;
&lt;li&gt;Sort alphabetically for better organization&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You're stuck scrolling through an unsorted mess.&lt;/p&gt;

&lt;h2&gt;
  
  
  My Solution ✨
&lt;/h2&gt;

&lt;p&gt;Meet the &lt;strong&gt;Cloudflare R2 Bucket Sorter&lt;/strong&gt; - a Chrome extension that adds sorting directly to your R2 dashboard.&lt;/p&gt;

&lt;h3&gt;
  
  
  What it does:
&lt;/h3&gt;

&lt;p&gt;🗂️ Sort by file size, modified date, or filename&lt;br&gt;&lt;br&gt;
📊 Shows total item count and combined file size&lt;br&gt;&lt;br&gt;
💾 Remembers your preferences&lt;br&gt;&lt;br&gt;
⚡ Works instantly with existing dashboard  &lt;/p&gt;
&lt;h2&gt;
  
  
  How It Works 🔧
&lt;/h2&gt;

&lt;p&gt;Instead of fighting with Cloudflare's API, I took a simpler approach: &lt;strong&gt;DOM manipulation&lt;/strong&gt;. The extension:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Detects when you're on an R2 bucket page&lt;/li&gt;
&lt;li&gt;Injects a floating control panel&lt;/li&gt;
&lt;li&gt;Sorts the existing table rows in real-time&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here's the core sorting logic:&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="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;sortTableRowsWithDOM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sortBy&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;order&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;table&lt;/span&gt; &lt;span class="o"&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;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;div[role="table"]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;rows&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;table&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelectorAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;div[role="row"]&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="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt; &lt;span class="o"&gt;=&amp;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;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;[data-testid*="object"]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

  &lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;rowA&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;rowB&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;compareTableRows&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rowA&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;rowB&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;sortBy&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

  &lt;span class="c1"&gt;// Efficiently reorder DOM elements&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fragment&lt;/span&gt; &lt;span class="o"&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;createDocumentFragment&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;fragment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;appendChild&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;table&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;appendChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fragment&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 trickiest part was parsing file sizes like "1.5 MB" into bytes for accurate comparison:&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="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;parseSizeToBytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sizeText&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;match&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;sizeText&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;(\d&lt;/span&gt;&lt;span class="sr"&gt;+&lt;/span&gt;&lt;span class="se"&gt;(?:\.\d&lt;/span&gt;&lt;span class="sr"&gt;+&lt;/span&gt;&lt;span class="se"&gt;)?)\s&lt;/span&gt;&lt;span class="sr"&gt;*&lt;/span&gt;&lt;span class="se"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;B|KB|MB|GB|TB&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="sr"&gt;/i&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;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;parseFloat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;match&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&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;multipliers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;B&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;KB&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;MB&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;GB&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;TB&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="mi"&gt;4&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;value&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;multipliers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;match&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;toUpperCase&lt;/span&gt;&lt;span class="p"&gt;()]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="mi"&gt;1&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;h2&gt;
  
  
  Key Challenges 💡
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Dynamic Content&lt;/strong&gt;: Cloudflare's SPA loads content dynamically, so I used &lt;code&gt;MutationObserver&lt;/code&gt; to detect page changes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reliable Selectors&lt;/strong&gt;: Cloudflare uses &lt;code&gt;data-testid&lt;/code&gt; attributes, which could change. I built fallback selectors to handle different table structures.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Performance&lt;/strong&gt;: Sorting hundreds of DOM elements efficiently using &lt;code&gt;DocumentFragment&lt;/code&gt; for batch updates.&lt;/p&gt;

&lt;h2&gt;
  
  
  Privacy First 🔒
&lt;/h2&gt;

&lt;p&gt;The extension:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Only works on Cloudflare dashboard pages&lt;/li&gt;
&lt;li&gt;Stores preferences locally (no external servers)&lt;/li&gt;
&lt;li&gt;Requires no API keys or authentication&lt;/li&gt;
&lt;li&gt;Uses pure DOM manipulation&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Want to Try It? 🚀
&lt;/h2&gt;

&lt;p&gt;I'd love for you to test the extension and share feedback! &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Installation:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Download from &lt;a href="https://github.com/tarek-gritli/r2-bucket-sorter" rel="noopener noreferrer"&gt;GitHub Repository&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Go to &lt;code&gt;chrome://extensions/&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Enable "Developer mode" &lt;/li&gt;
&lt;li&gt;Click "Load unpacked" and select the folder&lt;/li&gt;
&lt;li&gt;Navigate to any R2 bucket - the sorting panel appears automatically!&lt;/li&gt;
&lt;/ol&gt;




&lt;p&gt;&lt;strong&gt;Have you tried the extension? Run into any bugs? Want a new feature?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Drop a comment below or open an issue on GitHub! I'm actively maintaining this project and would love your input.&lt;/p&gt;

</description>
      <category>extensions</category>
      <category>javascript</category>
      <category>cloudflare</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
