Files & Blobs
Read, create, preview, and download browser files with Blob, File, FileReader, object URLs, and drag-and-drop without uploading private data.
- 01Explain Blob and FileTreat file data as bytes, labels, names, and last-modified metadata.
- 02Read and preview safelyChoose text, bytes, streams, FileReader events, and object URLs for realistic UI jobs.
- 03Handle local filesUse file inputs and drop zones while respecting privacy, cleanup, and browser limits.
Boxes of bytes the browser can hold
Web apps often need to work with local or generated files: preview an image before upload, export a CSV report, let a drawing app download a PNG, inspect a dropped text file, or store a draft for later. The browser gives JavaScript two central objects for this work: Blob and File.
A Blob is a sequence of bytes with a MIME type label. A File is a Blob with file metadata such as a name and a last-modified time. You do not get to browse the visitor’s disk. The user must choose or drop files, and even then the browser exposes the filename, not the full private path.
Use Blob methods such as text(), arrayBuffer(), stream(), and slice() to read or split bytes. Use object URLs when an element needs a URL for those bytes. Use FileReader when you need old-style events like progress and abort.
Imagine a box of parts sealed with tape. The outside label says what the sender claims it is. You can weigh the box, cut a smaller piece from it, or hand it to someone else. To see the parts, someone has to open it. Blob APIs work the same way: metadata is cheap, reading bytes is a separate action.
- In real life: A sealed box
- In JavaScript: A Blob: bytes you cannot see until you read them
- In real life: A label saying “text/plain”
- In JavaScript: The Blob’s
typestring - In real life: Cutting off one corner of the box
- In JavaScript:
blob.slice(start, end) - In real life: Opening the box
- In JavaScript:
text(),arrayBuffer(),stream(), or FileReader
Where the analogy stops: A real box proves what is inside when you open it. A Blob’s type label is not proof; code can put any label on any bytes.
This lesson builds that mental model, then turns it into browser UI: a Blob builder, a FileReader event replay, a progress reader, and a local file inspector with drag-and-drop.
Blob and File
CORE MODELBlob stands for “binary large object,” but the everyday meaning is simpler: some bytes JavaScript can pass around. A Blob may contain text, JSON, an image, a ZIP file, audio, or bytes you made yourself. It has two always-available properties: size in bytes and type, a MIME label like text/plain or an empty string.
A File is more specific. It is what a file input or drop zone usually gives you. It has every Blob ability, plus name and lastModified. That means any function that accepts a Blob can accept a File too.
| Question | Blob | File |
|---|---|---|
| What it represents | Bytes plus a type label | A Blob plus file metadata |
| Extra metadata | Only size and type | Also name and lastModified |
| Created by | new Blob([...]), Response.blob(), canvas exports | File inputs, drag-and-drop, or new File([...], name) |
| Can be read with | text(), arrayBuffer(), stream(), FileReader | The same methods, because File extends Blob |
The type string deserves respect and suspicion. It helps pick a preview strategy, but it is not verified by the browser. A text file can be mislabeled. A user’s operating system can report an empty type. A malicious upload can lie. Treat client-side labels as hints and validate important files on the server.
Build a Blob and read it back
INTERACTIVELet’s create a Blob from text. Strings become bytes when they are added to a Blob. The browser encodes them, usually as UTF-8. That is why a string’s length and a Blob’s size can differ: accents and emoji need more than one byte.
const text = "Hello, café 🌍";const blob = new Blob([text], { type: "text/plain" });console.log(text.length);console.log(blob.size);console.log(blob.type);console.log(await blob.text());console.log([...new Uint8Array(await blob.arrayBuffer())].slice(0, 8));console.log(await blob.slice(7, 12).text());- String length
- 14
- Blob size
- 17 bytes
- Type label
- text/plain
- Slice preview
- Build to read slice
First bytes: Build to call arrayBuffer()
Text read back: Build to await blob.text()
Type text, especially accented letters or the globe emoji, then build. The type is only a label; the bytes are whatever text you supplied.
Try café, then try hello, then add the globe character from the starter text. The string length tells you about JavaScript’s string representation; blob.size tells you how many bytes are in the sealed box. For the deeper string side, see Unicode and string internals.
The builder also reads the same Blob in three different ways: text() turns it back into a string, arrayBuffer() reveals bytes, and slice() creates a smaller Blob from a byte range. None of those changes the original Blob.
Object URLs: temporary tickets for bytes
PREVIEWMany browser elements need URLs. An image preview wants an src. A download link wants an href. A video player wants a source. A Blob is not a URL, so the browser lets you ask for a temporary one with URL.createObjectURL(blob).
A download link points to a file without putting the whole file in the link text. An object URL works the same way: it is a short reference, not a base64 copy of every byte.
- In real life: A file on your computer
- In JavaScript: The Blob or File kept by the browser
- In real life: The download link
- In JavaScript: The temporary
blob:URL - In real life: Opening the link
- In JavaScript: An element loading
srcorhref - In real life: Removing the link
- In JavaScript:
URL.revokeObjectURL(url)
Where the analogy stops: A normal download link can point to the internet. An object URL points to bytes in this document and can be revoked early.
const blob = new Blob(["download me"], { type: "text/plain" });const url = URL.createObjectURL(blob);link.href = url;link.download = "note.txt"; // Later, when the preview or link is no longer needed:URL.revokeObjectURL(url);Keep object URLs only while the page needs them. When a preview is replaced, a Reset button clears it, or a React component unmounts, revoke the old URL. That cleanup tells the browser it may release the underlying bytes. Do not revoke immediately before an image has loaded or a click has used the link.
Object URLs are different from data URLs. A data URL embeds base64 text directly in the URL and is often about one third larger. FileReader’s readAsDataURL() can create one, but object URLs are usually better for previews and downloads.
FileReader: an older delivery tracker
STEP THROUGHModern Blob methods are promise-based: await blob.text() gives you the whole text, and await blob.arrayBuffer() gives you the bytes. FileReader is older. You create a reader, attach event listeners, then start a read with methods such as readAsText(), readAsArrayBuffer(), or readAsDataURL().
An older delivery tracker gives you updates as a package moves. FileReader does the same with events for progress, abort, and errors.
- In real life: Tracking starts
- In JavaScript:
loadstart - In real life: The progress bar moves
- In JavaScript:
progress - In real life: The delivery finishes
- In JavaScript:
load - In real life: Tracking closes either way
- In JavaScript:
loadend
Where the analogy stops: A real tracker can be late or wrong. FileReader events are asynchronous, and their progress counts vary.
Switch the setting, predict the event order, then step through the modeled FileReader run.
script
const reader = makeReader("success"); reader.addEventListener("loadstart", () => events.push("loadstart"));reader.addEventListener("progress", () => events.push("progress"));reader.addEventListener("load", () => events.push("load"));reader.addEventListener("abort", () => events.push("abort"));reader.addEventListener("error", () => events.push("error"));reader.addEventListener("loadend", () => events.push("loadend")); reader.readAsText(blob);console.log(events.join(" -> "));On success you normally see loadstart, some number of progress events, load, and loadend. If the read is aborted, abort happens before the final loadend. If it fails, error happens before loadend. For basic whole-file reads, promises are easier; for visible progress, FileReader still has a job.
const blob = new Blob([largeBuffer], { type: "application/octet-stream" });const reader = new FileReader(); reader.addEventListener("progress", (event) => { if (event.lengthComputable) { console.log(Math.round((event.loaded / event.total) * 100) + "%"); } else { console.log(event.loaded + " bytes read"); }}); reader.addEventListener("load", () => console.log(reader.result.byteLength));reader.readAsArrayBuffer(blob);- 1
Press Read generated Blob. No local file is touched.
FileReader reports progress events while it reads. Browsers decide chunk sizes, so you may see one event or several. The generated Blob is local to this page.
Inspect files from an input or a drop zone
INTERACTIVEA file input and a drop zone both put File objects in your hands after the user chooses to share them with the page. The page can then inspect metadata, read the bytes, or send the file later. Merely selecting or dropping a file does not upload it.
dropZone.addEventListener("dragover", (event) => { event.preventDefault();}); dropZone.addEventListener("drop", async (event) => { event.preventDefault(); const [file] = event.dataTransfer.files; if (!file) return; console.log(file.name, file.size, file.type, file.lastModified); if (file.type.startsWith("image/")) { preview.src = URL.createObjectURL(file); } else if (file.type.startsWith("text/") || file.name.endsWith(".txt")) { preview.textContent = (await file.text()).slice(0, 400); }});No file selected yet.
Choose a file or drop one here. Nothing is uploaded.
The important drag-and-drop detail is dragover. Calling event.preventDefault() there tells the browser your element is a valid drop target. In drop, event.dataTransfer.files contains the files. Some folder drops do not produce ordinary File objects, so a friendly file UI handles an empty list without crashing.
When you build real upload forms, combine these File objects with the ideas from FormData. When you store generated files or offline responses, compare the tools in Cache API and file storage.
Choose the right reading tool
SORTEvery file task asks a different question. Do you need text, exact bytes, a stream of chunks, a preview URL, a self-contained base64 URL, or progress events? Pick the API that matches the job instead of memorizing one “file reading” method.
| Task | Good tool | Why |
|---|---|---|
| Task | Good tool | Why |
| Read a text file | await file.text() | Short, modern, promise-based code. |
| Parse bytes | await file.arrayBuffer() | Gives exact bytes for binary formats. |
| Show an image preview | URL.createObjectURL(file) | The browser decodes from a temporary URL without base64 expansion. |
| Show progress or abort | FileReader | It exposes progress, abort, error, and loadend events. |
| Process huge data in chunks | blob.stream() | Streams fit chunk-by-chunk pipelines. |
blob.text()file.text()blob.arrayBuffer()blob.stream()URL.createObjectURL(blob)readAsDataURL(file)- FileReader progress events
- <a download> with an object URL
Place each API or pattern under the job it is best suited for.
If blob.stream() looks interesting, the Streams and progress lesson explains chunk-by-chunk processing. This lesson’s main rule is simpler: whole text, whole bytes, stream chunks, preview URL, or events.
Where you will use this
PRACTICALFiles and Blobs show up whenever a browser app touches user data without a page refresh. A few realistic patterns:
- Client-side export: turn rows into CSV text, create a Blob, make an object URL, and attach it to an
<a download>. - Preview before upload: use a File from an input, show images with an object URL, and show text with
file.text(). - Generated media: canvas and media APIs can produce Blobs that become downloads or previews.
- Offline workflows: store file-shaped data in OPFS or IndexedDB, and cache network Responses for app shells.
The browser never hands your page a full local path like /Users/ada/taxes/receipt.pdf. It gives you the file name and bytes only after a user action. That is by design.
Common pitfalls
HONEST EDGE CASES- “Blob size is string length.” No. Blob size is bytes after encoding. Text with accents or emoji proves the difference.
- “The type proves the file format.” No.
typeis a label and can be empty or wrong. - “Selecting a file uploads it.” No. Uploading requires your code to send it, often with FormData and fetch.
- “Object URLs clean themselves instantly.” They last until revoked or the document goes away. Revoke long-lived previews.
- “FileReader is the only way to read files.” Modern Blob methods are usually simpler unless you need events.
- “The browser exposes the full path.” It exposes the file name, not the private folder path.
Practice exercises
5 EXERCISESPredict the two console lines.
const blob = new Blob(["café"], { type: "text/plain" });
console.log("café".length);
console.log(blob.size);const blob = new Blob(["café"], { type: "text/plain" });
console.log("café".length);
console.log(blob.size);The string has four JavaScript characters, but the Blob stores five UTF-8 bytes.
Run the code in a modern browser console or Node 22 and write the three outputs.
const file = new File(["hello"], "hello.txt", { type: "text/plain", lastModified: 10 });
console.log(file instanceof Blob);
console.log(file.name);
console.log(file.size);const file = new File(["hello"], "hello.txt", { type: "text/plain", lastModified: 10 });
console.log(file instanceof Blob);
console.log(file.name);
console.log(file.size);The first line is true, then the File metadata and Blob size are printed.
Which event should cleanup code listen for if it must run after any FileReader outcome?
loadend always marks the end of a FileReader operation, whether it loaded, aborted, or errored.
A component creates a new image preview every time a user chooses a file. What method should it call for the previous preview URL?
URL.revokeObjectURL(url);Revoke the object URL when replacing or removing the preview so the browser can release it.
Your UI wants to display where a dropped file came from. Can browser JavaScript show the full path?
No. Show file.name, size, type, and lastModified, but do not claim you know the full local path.
Check your understanding
8 QUESTIONSQuestion 1 of 8What does
Blob.sizemeasure?Choose an answer to see the explanation.
Question 2 of 8What does this Blob program print?
Read the code, then predictconst blob = new Blob(["café"], { type: "text/plain" }); console.log("café".length); console.log(blob.size);Choose an answer to see the explanation.
Question 3 of 8Which statement about
typeis true?Choose an answer to see the explanation.
Question 4 of 8What does this File program print?
Read the code, then predictconst file = new File(["hello"], "hello.txt", { type: "text/plain", lastModified: 10 }); console.log(file instanceof Blob); console.log(file.name); console.log(file.size);Choose an answer to see the explanation.
Question 5 of 8When should you revoke an object URL?
Choose an answer to see the explanation.
Question 6 of 8Which FileReader order is the usual success path in this lesson's model?
Choose an answer to see the explanation.
Question 7 of 8Which tool is best for a simple text file read?
Choose an answer to see the explanation.
Question 8 of 8What must a drop zone do in
dragoverto receive a drop?Choose an answer to see the explanation.
Key takeaways
- A Blob is a byte box with a type label; a File is a Blob with a name and last-modified time.
- Blob size is bytes, not string length. Unicode text makes that difference visible.
- Use
text(),arrayBuffer(), andstream()for modern reads; use FileReader for progress, abort, and legacy data URL flows. - Object URLs are temporary tickets for previews and downloads. Revoke them when they are no longer needed.
- File inputs and drop zones never reveal full local paths and never upload by themselves.
Final definition.
Files and Blobs are browser objects that let JavaScript hold, inspect, read, preview, and download bytes chosen or generated in the page.
Up next: Device & platform APIs.