<?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: Fahad Farid</title>
    <description>The latest articles on DEV Community by Fahad Farid (@toolvero).</description>
    <link>https://dev.to/toolvero</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%2F4183099%2F6e6e3bf5-2e6d-4efc-a4c9-134f45968eb3.png</url>
      <title>DEV Community: Fahad Farid</title>
      <link>https://dev.to/toolvero</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/toolvero"/>
    <language>en</language>
    <item>
      <title>How I strip photo metadata without a server</title>
      <dc:creator>Fahad Farid</dc:creator>
      <pubDate>Tue, 13 Oct 2026 20:58:02 +0000</pubDate>
      <link>https://dev.to/toolvero/how-i-strip-photo-metadata-without-a-server-dn0</link>
      <guid>https://dev.to/toolvero/how-i-strip-photo-metadata-without-a-server-dn0</guid>
      <description>&lt;p&gt;Your phone embeds a small diary in every photo: GPS coordinates, camera model, timestamps. Most “metadata remover” sites ask you to upload that diary to &lt;em&gt;their&lt;/em&gt; server first — which rather defeats the purpose.&lt;/p&gt;

&lt;p&gt;I build &lt;a href="https://toolvero.co" rel="noopener noreferrer"&gt;ToolVero&lt;/a&gt;, a set of 70+ free tools that run entirely in your browser, and the image metadata remover takes the opposite approach: the photo never leaves the device. Here’s the whole trick.&lt;/p&gt;

&lt;h3&gt;
  
  
  The method: decode, then re-encode from pixels
&lt;/h3&gt;

&lt;p&gt;EXIF, GPS and XMP all live in the JPEG’s metadata segments — they are not part of the image data itself. So instead of surgically deleting segments, we throw the container away:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Decode the uploaded JPEG into a canvas bitmap (pixels only — the browser’s image decoder does not carry EXIF into the bitmap).&lt;/li&gt;
&lt;li&gt;Create a fresh PNG from those pixels with &lt;code&gt;canvas.toBlob('image/png')&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Offer that PNG as the download, under a neutral filename.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A PNG produced this way contains only IHDR, IDAT and IEND chunks. There is simply nowhere for EXIF to hide, because we never copied the original file’s segments — we re-created the image from raw pixels.&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;const&lt;/span&gt; &lt;span class="nx"&gt;img&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;createImageBitmap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;   &lt;span class="c1"&gt;// pixels only, no EXIF&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;canvas&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;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;canvas&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;img&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;height&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;img&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;height&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2d&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;drawImage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;img&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&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;clean&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toBlob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;image/png&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="c1"&gt;// `clean` is a fresh PNG: IHDR + IDAT + IEND, zero metadata segments&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Why the output is clean by construction
&lt;/h3&gt;

&lt;p&gt;PNG has no EXIF segment to preserve, and this method never copies the original file’s metadata segments at all — only the pixels. So EXIF, GPS and XMP have nowhere to survive. (Re-encoding to JPEG via canvas is equally clean, since canvas never writes EXIF — but PNG is the safer default because the format itself can’t carry the metadata.)&lt;/p&gt;

&lt;h3&gt;
  
  
  Why client-side matters
&lt;/h3&gt;

&lt;p&gt;The privacy win isn’t just the stripped file — it’s that the stripping happens on your machine. No upload means no transit interception, no server logs, no “we delete after 24 hours” promises to trust. For a privacy tool, the architecture &lt;em&gt;is&lt;/em&gt; the feature.&lt;/p&gt;

&lt;p&gt;Try it on your own photos: &lt;a href="https://toolvero.co/tools/image-metadata-remover/" rel="noopener noreferrer"&gt;Image Metadata Remover&lt;/a&gt; — everything runs locally in the tab.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>privacy</category>
      <category>webdev</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
