Why Indian government portals reject your photo upload (and how to validate before you submit)

Every Indian portal wants a photo, and each one wants a different file. Not different content — different dimensions and byte budgets. If you build a tool that produces them, or you are just trying to get through an application, the rejections come from four specific places.

The four documents, four contracts

Document Physical Pixels Bytes
Passport 35 × 45 mm 413 × 531 @300 DPI 10–250 KB
Visa / e-Visa 51 × 51 mm (square) 350–1000 px 10 KB – 1 MB
PAN card 25 × 35 mm (landscape) 295 × 413 ≤ 50 KB (+ signature ≤ 50 KB)
OCI 35 × 35 mm (square) 600 × 600 per portal

The passport one is the strictest and the most commonly failed. Note the byte window is bounded on both sides: under 10 KB and the portal may reject it as too low quality; over 250 KB and it refuses to upload at all.

Why this breaks naive uploads

Three failure modes account for most rejections, and none of them are about the face.

1. The KB budget is a two-sided constraint

Most browser crop tools do canvas.toBlob(blob => ..., 'image/jpeg', 0.9) and hope. A 413 × 531 output at quality 0.9 lands around 60–90 KB, which is fine — until someone uploads a 12 MP phone photo and the tool resizes after encoding, producing a file that is either 400 KB or visibly soft.

The reliable pattern is to binary-search the quality so you land inside the window rather than under a ceiling:

async function within(canvas, minKB, maxKB) { let lo = 0.3, hi = 0.95, best = null; for (let i = 0; i < 7; i++) { const q = (lo + hi) / 2; const blob = await new Promise(r => canvas.toBlob(r, 'image/jpeg', q)); const kb = blob.size / 1024; if (kb >= minKB && kb <= maxKB) return blob; if (kb > maxKB) hi = q; else lo = q; best = blob; } return best; } 

Seven encodes is roughly 40 ms on a mid-range Android for a 413 × 531 canvas.

2. Square vs landscape is a real branch, not a detail

The visa photo is square (51 × 51 mm). The OCI photo is square but smaller (35 × 35 mm). The passport photo is portrait 35 × 45 mm. The PAN photo is landscape 25 × 35 mm — 35 mm wide, 25 mm tall.

If your code has a single crop(w, h) call and assumes height ≥ width, the PAN upload is silently wrong: it will look correct on screen and be rejected by the portal. Store the spec as data:

const SPECS = { 'in-passport': { wMm: 35, hMm: 45, dpi: 300, minKB: 10, maxKB: 250, headRatio: [0.80, 0.85] }, 'in-visa': { wMm: 51, hMm: 51, dpi: 300, minKB: 10, maxKB: 1024, headRatio: [0.49, 0.69] }, 'in-pan': { wMm: 35, hMm: 25, dpi: 600, minKB: 0, maxKB: 50, headRatio: null }, 'in-oci': { wMm: 35, hMm: 35, dpi: 600, minKB: 0, maxKB: 200, headRatio: [0.70, 0.80] }, }; 

Once width and height are separate fields, the landscape case stops being a special case. The exact numbers for each are on our Indian passport photo and India visa photo pages; PAN and OCI follow the same shape (PAN, OCI).

3. DPI is metadata, and portals mostly ignore it

For an online upload the portal reads pixels and bytes. DPI only matters when you print. That means "600 DPI" in a spec is really a statement about the print size, and the digital file just needs enough pixels to satisfy it: 35 × 25 mm at 600 DPI is 827 × 591 px for print, while the upload accepts 295 × 413.

Confusing the two produces files that are either needlessly huge (rejected for size) or too small to print (rejected at the counter). Keep dpi and px as separate outputs of the same spec.

A pre-submit checklist that actually catches failures

Before the user hits upload, check these four things and show the failing one by name:

  1. Pixel dimensions exactly match the spec for that document (not the aspect ratio — the dimensions).
  2. File size is inside the window, both bounds.
  3. Format is really JPEG: a HEIC renamed to .jpg passes the extension check and fails the decoder. Verify with magic bytes (FF D8 FF), not the filename.
  4. Head ratio is inside the range, measured from the detected or user-marked face box.

Doing this client-side turns a confusing portal error into a specific instruction, which is the difference between a user retrying once and a user giving up.

I work on Passport Size Photo, which generates these four Indian formats (and about 530 others) from a single selfie — the spec table above is the same one the tool runs on.