Browser File API Deep Dive: Privacy-First Local File Handling
Evolution of Browser File Handling
Early web apps relied on <input type="file"> plus server uploads. After HTML5, the File API let JavaScript read local files without uploading—a shift that enabled “browser-local tools”: PDF editing, image compression, and video transcoding entirely on the user’s device, with files never leaving the browser.
Core API Layers
File (user-selected file with name/size/type/lastModified)
↓ extends
Blob (binary large object with size/type)
↓ read as
ArrayBuffer (raw bytes—for WASM/crypto)
↓ or
Text (UTF-8 string—for JSON/CSV)
↓ or
Data URL (Base64—for inline display)
↓ output as
Blob → Object URL → download / preview
File vs Blob
// File is a Blob subclass with name and lastModified
const input = document.querySelector('input[type="file"]');
input.addEventListener('change', (e) => {
const file: File = e.target.files[0];
console.log(file.name); // "report.pdf"
console.log(file.size); // 2048576 (bytes)
console.log(file.type); // "application/pdf"
console.log(file.lastModified); // 1717200000000
});
Reading methods compared
| Method | Return type | Use case | Memory |
|---|---|---|---|
file.text() |
string | JSON, CSV, text | Full load |
file.arrayBuffer() |
ArrayBuffer | PDF, images, WASM input | Full load |
file.stream() |
ReadableStream | Large files, chunked | Chunked read |
FileReader.readAsDataURL() |
Data URL | Image preview | Base64 +33% size |
ToolsKu strategy: < 50MB use arrayBuffer(), > 50MB use stream() chunking.
Object URL: Zero-Copy Preview and Download
// Temporary URL (in-memory reference, not a disk file)
const blob = new Blob([pdfBytes], { type: 'application/pdf' });
const url = URL.createObjectURL(blob);
// Preview
iframe.src = url;
// Download
const a = document.createElement('a');
a.href = url;
a.download = 'merged.pdf';
a.click();
// ⚠️ Must revoke or memory leaks
URL.revokeObjectURL(url);
Object URLs use the blob: scheme and point at in-memory Blobs. Each unreleased URL holds memory—revoke promptly during batch processing.
Drag-and-Drop Upload
const dropZone = document.getElementById('drop-zone');
dropZone.addEventListener('dragover', (e) => {
e.preventDefault(); // Required or the browser opens the file
e.dataTransfer.dropEffect = 'copy';
});
dropZone.addEventListener('drop', (e) => {
e.preventDefault();
const files = Array.from(e.dataTransfer.files);
const pdfs = files.filter(f => f.type === 'application/pdf');
processFiles(pdfs);
});
DataTransfer.files returns a FileList compatible with <input type="file">’s files.
Privacy Architecture: Why Local Processing Is Safer
Traditional online tool data flow
User file → HTTPS upload → Server processing → Download result → Server delete (claimed)
↑
May be cached, logged, backed up
ToolsKu data flow
User file → File API read → Browser memory/WASM → Blob download
↑
Never leaves the device
| Dimension | Server processing | Browser-local |
|---|---|---|
| Transfer | Upload + download | Zero network |
| Privacy risk | Server may retain | Cleared when tab closes |
| Speed | Network-bound | CPU-bound |
| Size limit | Often 10–50MB | Device RAM |
| Offline | No | Yes (with PWA cache) |
Streams API: Very Large Files
Videos over 500MB loaded entirely into ArrayBuffer can OOM:
async function processLargeFile(file: File) {
const stream = file.stream();
const reader = stream.getReader();
const chunks: Uint8Array[] = [];
while (true) {
const { done, value } = await reader.read();
if (done) break;
chunks.push(value); // One chunk at a time (often 64KB)
}
const totalLength = chunks.reduce((sum, c) => sum + c.length, 0);
const result = new Uint8Array(totalLength);
let offset = 0;
for (const chunk of chunks) {
result.set(chunk, offset);
offset += chunk.length;
}
return result;
}
FFmpeg.wasm can write Uint8Array into its virtual FS; with Streams, multi-GB video is possible when RAM allows.
File Type Detection: MIME vs Magic Bytes
File.type comes from OS extension mapping and is unreliable:
// ❌ User can rename .exe to .pdf
file.type === 'application/pdf' // May be true but not a real PDF
// ✅ Read magic bytes in header
const header = new Uint8Array(await file.slice(0, 5).arrayBuffer());
const isPdf = header[0] === 0x25 && header[1] === 0x50 &&
header[2] === 0x44 && header[3] === 0x46; // %PDF
ToolsKu’s MIME sniffing tool uses magic bytes—more accurate than extensions.
Download Best Practices
Trigger download
function downloadBlob(blob: Blob, filename: string) {
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
}
Batch download: ZIP
Don’t trigger many downloads in a row (browsers block pop-ups). ToolsKu uses JSZip:
PDF to images → 50 PNGs → JSZip → download images.zip
Summary
The File API is the foundation of browser-local tools. Mastering File → Blob → ArrayBuffer, Object URL lifecycle, and Streams for large files is core to privacy-first web tools. ToolsKu’s 200+ tools follow this model: no upload, local processing, cleared on close.
Try these browser-local tools — no sign-up required →