<?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: Vaibhav Agrawal</title>
    <description>The latest articles on DEV Community by Vaibhav Agrawal (@vaibhav_agrawal).</description>
    <link>https://dev.to/vaibhav_agrawal</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%2F4112661%2F369ab97b-b537-40e3-aa69-3b6e74d3d1b6.png</url>
      <title>DEV Community: Vaibhav Agrawal</title>
      <link>https://dev.to/vaibhav_agrawal</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/vaibhav_agrawal"/>
    <language>en</language>
    <item>
      <title>So, I Built a Tool That Makes Any Photo Pass a Visa Portal's Upload Rules. I Call It Fitframe</title>
      <dc:creator>Vaibhav Agrawal</dc:creator>
      <pubDate>Sun, 06 Sep 2026 17:54:19 +0000</pubDate>
      <link>https://dev.to/vaibhav_agrawal/so-i-built-a-tool-that-makes-any-photo-pass-a-visa-portals-upload-rules-i-call-it-fitframe-217l</link>
      <guid>https://dev.to/vaibhav_agrawal/so-i-built-a-tool-that-makes-any-photo-pass-a-visa-portals-upload-rules-i-call-it-fitframe-217l</guid>
      <description>&lt;p&gt;A few weeks ago I was filling in yet another government form and hit the line I've hit a dozen times before:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Photograph: 3.5 cm × 4.5 cm, JPEG format, file size between 20 KB and 50 KB."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I had the photo. It was 3,024 × 4,032 pixels and 3 MB, because that's what a phone camera produces. The portal wanted something that was, in every dimension, about 1% of that.&lt;/p&gt;

&lt;p&gt;So I opened one of those "free online photo resizer" sites, uploaded my passport photo to a server I know nothing about, fiddled with a quality slider, downloaded, got rejected for being 53 KB, and did it again.&lt;/p&gt;

&lt;p&gt;Every portal wants something different. The US DS-160 wants a 600 × 600 px square under 240 KB. India's e-Visa wants a JPEG between 10 KB and 1 MB. SSC and UPSC want 35 × 45 mm at 20–50 KB. The UK wants at least 600 × 750. None of them want your camera's EXIF data. And several of them have a minimum file size, which is a rule no consumer tool seems to know exists.&lt;/p&gt;

&lt;p&gt;So I built Fitframe.(&lt;a href="http://www.getfitframe.com" rel="noopener noreferrer"&gt;www.getfitframe.com&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3iz4djicqru4pkgb17eq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F3iz4djicqru4pkgb17eq.png" alt=" " width="799" height="512"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How it works
&lt;/h2&gt;

&lt;p&gt;The flow is deliberately short, because the whole point is to stop wasting time on this.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Drop in a photo File picker, drag-and-drop, or paste from the clipboard. Phone photos with rotation metadata are handled correctly.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Set the portal's rules Width and height in px, mm or inches (with DPI), a minimum and maximum file size in KB, and the format. There are presets for the common cases, but every field is editable because every portal is slightly different.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Frame the shot This is the part other tools skip. Your photo is almost never the same shape as the target, so you have to choose what gets cut. Fitframe draws a frame at the exact target proportions with a live ruler in real units, and you drag, zoom or pinch the photo inside it. Or switch to "Pad with white" to keep the whole photo and fill the gaps.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Faqzjgupvn6bgei8f386e.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Faqzjgupvn6bgei8f386e.png" alt=" " width="800" height="733"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Make it fit One button. It resizes to the exact pixel dimensions, then tunes the JPEG quality until the file lands just under the size limit. You get a pass/fail on each rule and a download.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4q9dq0jc8hb3zu7n158t.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4q9dq0jc8hb3zu7n158t.png" alt=" " width="799" height="512"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmfa5binb10f2nybrsxn5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmfa5binb10f2nybrsxn5.png" alt=" " width="800" height="751"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;My favourite feature: minimum file size&lt;/p&gt;

&lt;p&gt;Everyone knows how to make a file smaller. Almost nobody thinks about making it bigger.&lt;/p&gt;

&lt;p&gt;Indian exam portals in particular love a floor: "20 KB to 50 KB". A clean, well-lit photo at 413 × 531 px can compress to 14 KB at full quality, and the portal rejects it for being too small. There is no slider for that.&lt;/p&gt;

&lt;p&gt;Fitframe's approach: first it tries raising the quality. If the file is still under the floor, it inserts a JPEG comment segment (or a PNG text chunk) filled with whitespace to reach the minimum. The result is a completely valid image that every decoder opens normally; it just carries a few kilobytes of nothing. I verified the padded outputs with Pillow to make sure nothing downstream would choke.&lt;/p&gt;

&lt;p&gt;It also applies the limits slightly conservatively: 1,000 bytes per KB for the maximum and 1,024 for the minimum, so a file that passes in Fitframe passes whichever way a portal counts kilobytes.&lt;/p&gt;

&lt;p&gt;And when the rules are genuinely impossible, it tells you so instead of silently handing you a broken file.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6w17ppeb4iz5rpvzvtkz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6w17ppeb4iz5rpvzvtkz.png" alt=" " width="800" height="878"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Nothing gets uploaded
&lt;/h2&gt;

&lt;p&gt;A passport photo is a piece of identity. It shouldn't go to a random server so it can be shrunk.&lt;/p&gt;

&lt;p&gt;Fitframe runs entirely in the browser: canvas for the crop and resize, toBlob for encoding, a bit of byte-level work for the padding. There is no backend. The site is a single HTML file. Turn off Wi-Fi after the page loads and it still works.&lt;/p&gt;

&lt;p&gt;Re-encoding through canvas also strips EXIF as a side effect, which is a requirement I didn't know about until a portal rejected a photo for containing "unsupported metadata".&lt;/p&gt;

&lt;h2&gt;
  
  
  Built with AI, but it's still just code
&lt;/h2&gt;

&lt;p&gt;I built this with Claude in a single sitting, and the thing I appreciated most is what came out the other end: one index.html, vanilla JavaScript, no framework, no build step, no dependencies.&lt;/p&gt;

&lt;p&gt;That matters for a tool like this. I can read the whole thing. When the notification-on-ready idea came up, that was a twenty-line change I could follow. When a portal changes its rules, updating a preset is editing one line in an array. There's nothing to npm install in two years when I want to fix something.&lt;/p&gt;

&lt;p&gt;A few decisions I'd defend:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Step-down resizing&lt;/strong&gt;. Going from 4,000 px to 500 px in one drawImage call produces aliasing. Fitframe halves the image repeatedly until it's within 2× of the target, then does the final resize. It's sharper, and it's about eight lines.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Binary search on quality, not a slider.&lt;/strong&gt; Ten encodes, each a few milliseconds, land within a kilobyte of the limit every time.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The frame is the interface.&lt;/strong&gt; Instead of separate crop and resize steps, you position the photo in a frame that already has the target's proportions. Everything inside the frame is the output. There's nothing to reconcile afterwards.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Deployment was drag-and-drop onto Vercel, plus two DNS records at the registrar. Analytics is Vercel's cookieless page-view counter, because a tool built around "your photo never leaves your browser" shouldn't ship with a tracking cookie.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;It's free, there's no signup, and there's nothing to install:&lt;/p&gt;

&lt;p&gt;getfitframe.com&lt;/p&gt;

&lt;p&gt;If a portal you use isn't in the presets, or has a rule Fitframe doesn't handle, tell me. Those are the exact things I want to add next.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>productivity</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
