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 FileSay what a Blob holds (bytes and a type label) and what a File adds (a name and a last-changed time).
- 02Read and preview filesPick the right tool for each job:
text(),arrayBuffer(),stream(), FileReader events, or an object URL. - 03Work with the user's filesUse a file input and a drop zone, cancel object URLs you no longer need, and keep the user's folders private.
Bytes the browser can hold
A computer stores every file as bytes. A byte is a small number from 0 to 255. Text, pictures, and music are all just long rows of bytes.
Web pages often need to work with files. For example, a page may want to:
- show a picture before you upload it,
- save a table of scores as a file on your computer,
- read a text file that you drop onto the page.
For this, the browser gives JavaScript two kinds of object: Blob and File.
- A Blob holds some bytes and a type label, such as
text/plain. - A File is a Blob with two more facts: a name and the time it was last changed.
Your page can't look around the visitor's computer. The visitor must pick a file or drop it onto the page. Even then, the page only learns the file's name, not the folder it came from.
To read the bytes in a Blob, use its methods: text(), arrayBuffer(), stream(), and slice(). When a page element needs a web address for the bytes, make an object URL. When you need to watch a read happen step by step, use FileReader.
Picture a box that is taped shut. A label on the outside says what is inside. You can weigh the box, cut a smaller piece off it, or hand it to someone. To see what is inside, someone has to open it. Checking the label is quick. Opening the box is a separate job.
- In real life: A sealed box
- In JavaScript: A Blob: bytes you can't see until you read them
- In real life: A label that says “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
In this lesson you will try four small tools in the page:
- a Blob builder that makes a Blob from your text,
- a step-through that shows
FileReaderevents in order, - a reader that shows progress while it reads,
- a file inspector where you can pick or drop your own file.
Blob and File
CORE MODELBlob is short for "binary large object". In plain words, a Blob is some bytes that JavaScript can pass around. Those bytes can be text, JSON, a picture, a ZIP file, or sound.
Every Blob has two properties you can always read:
size: how many bytes it holds.type: a MIME type, which is a short label for the kind of data, such astext/plainorimage/png. It can also be an empty string.
A File is what you usually get from a file picker or a drop onto the page. A File can do everything a Blob can do. It also has two more properties:
name: the file name, such ashello.txt.lastModified: when the file was last changed, as a number of milliseconds.
So any code that works with a Blob also works with a File.
size, and a type. The File from exercise 2 holds the same things, plus a name and a lastModified time.The table puts the two side by side. Read each row as one question about both.
| Question | Blob | File |
|---|---|---|
| What does it hold? | Bytes and a type label. | The same, plus facts about the file. |
| Which properties does it have? | Only size and type. | Also name and lastModified. |
| Where does it come from? | new Blob([...]), Response.blob(), or a canvas picture. | A file input, a drop, or new File([...], name). |
| How do you read it? | With text(), arrayBuffer(), stream(), or FileReader. | With the same methods, because a File is a Blob. |
Don't fully trust the type label. It helps you choose how to show a file, but the browser doesn't check it against the bytes:
- A text file can have the wrong label.
- The computer may give a file no label at all, so
typeis an empty string. - Someone who wants to cause harm can put a false label on a file on purpose.
Use the label as a hint in the page. When a file matters, the server must check it again.
Build a Blob and read it back
INTERACTIVELet's make a Blob from text. When text goes into a Blob, the browser turns each letter into bytes. It uses a system called UTF-8. In UTF-8, plain English letters take one byte each. Letters with accents, like é, take two bytes. An emoji, like the globe, takes four.
That is why a string's length and a Blob's size can be different numbers. length counts letters. size counts bytes.
"café" has 4 letters. In a Blob it becomes 5 bytes, because é needs 2 bytes.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
- Part of the Blob
- Build to read a slice
First bytes: Build to call arrayBuffer()
Text read back: Build to call blob.text()
Type some text, then press Build Blob. Try letters with accents or the globe emoji. The type is only a label: the bytes are the text you typed.
Try café, then hello, then put the globe from the starting text back in. The string length tells you how many letters JavaScript counts. blob.size tells you how many bytes are in the Blob. To learn more about how strings store letters, see Unicode and string internals.
The builder reads the same Blob in three ways:
text()turns the bytes back into a string.arrayBuffer()gives you the raw bytes. AnArrayBufferis a block of bytes; the code wraps it in aUint8Arrayso it can print each byte as a number.slice(start, end)makes a new, smaller Blob from part of the bytes.
text() and arrayBuffer() return promises: the answer comes a little later, so the code writes await to wait for it. None of these methods changes the original Blob.
A Blob can also be built from other Blobs. The code below joins two small Blobs into one.
const header = new Blob(["Name,Score\n"], { type: "text/csv" });
const rows = new Blob(["Asha,9\n"], { type: "text/csv" });
const report = new Blob([header, rows], { type: "text/csv" });
console.log(report.type);
console.log(report.size);Line 1 makes a Blob with the header line. Line 2 makes a Blob with one row. Line 3 puts both into a new Blob, in that order: header first, then the row. Line 4 prints text/csv. Line 5 prints 18, because the header has 11 bytes and the row has 7. The type is still only a label, so a server must check an uploaded report.
Object URLs: short web addresses for bytes
PREVIEWMany page elements need a web address, called a URL:
- An image needs a URL in its
src. - A download link needs a URL in its
href. - A video player needs a URL for its source.
A Blob is not a URL. So the browser lets you ask for a short, temporary one with URL.createObjectURL(blob). This is called an object URL. It starts with blob:, and it only works inside this page.
A download link on a website is short. It doesn't contain the file; it only says where the file is. When you click it, you get the file. When the site takes the link down, it stops working.
- In real life: A file on a website
- 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: Clicking the link
- In JavaScript: An element loading its
srcorhref - In real life: Taking the link down
- In JavaScript:
URL.revokeObjectURL(url)
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);Line 1 makes a Blob with the text download me. Line 2 asks for an object URL that points at it. Line 3 gives that URL to the link. Line 4 sets the download attribute, which suggests a file name, note.txt. Line 7 cancels the URL when it is no longer needed.
URL.createObjectURL(blob) makes a short blob: address that points at the Blob. The link uses that address, so clicking it downloads note.txt.const rows = [["Name", "Score"], ["Asha", 9]];
const csv = rows.map((row) => row.join(",")).join("\n");
const blob = new Blob([csv], { type: "text/csv" });
const url = URL.createObjectURL(blob);
link.href = url;
link.download = "scores.csv";
console.log(csv);A CSV file is a table written as text: one row per line, with commas between the cells. Line 1 has two rows. Line 2 joins each row with commas, then joins the rows with a line break. Line 3 puts that text in a Blob. Lines 4 to 6 make an object URL and give it to the link, with the file name scores.csv. Line 7 prints the CSV text.
Keep an object URL only while the page needs it. Cancel it with URL.revokeObjectURL(url) when:
- a new preview replaces the old one,
- a Reset button clears the preview,
- the part of the page that showed it is removed.
Cancelling tells the browser it may free the memory the bytes use. Don't cancel too early: wait until the image has loaded or the link has been clicked.
A data URL is a different thing. It puts the whole file inside the URL, written as base64 text. Base64 writes bytes using only letters, digits, and a few symbols, so it is about one third bigger than the bytes. FileReader's readAsDataURL() makes data URLs. For previews and downloads, object URLs are usually the better choice.
FileReader: the older way to read
STEP THROUGHThe Blob methods return promises. await blob.text() gives you all the text. await blob.arrayBuffer() gives you all the bytes.
FileReader is older, and it works with events instead. An event is a message the browser sends when something happens. You use it in three steps:
- Make a reader with
new FileReader(). - Add listeners, functions that run when each event arrives.
- Start the read with a method such as
readAsText(),readAsArrayBuffer(), orreadAsDataURL().
A parcel tracker sends you a message at each stage: the parcel left, it is on the way, it arrived. At the end, the tracking closes. FileReader sends an event at each stage of a read in the same way.
- In real life: Tracking starts
- In JavaScript:
loadstart - In real life: The progress bar moves
- In JavaScript:
progress - In real life: The parcel arrives
- In JavaScript:
load - In real life: Tracking closes, whatever happened
- In JavaScript:
loadend
Pick a path, guess the order of the events, then step through the code.
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(" -> "));The step-through uses a small pretend reader, makeReader, that fires the events in the same order as a real FileReader. A real reader fires them a little later, not all at once.
The order depends on how the read ends:
- Success:
loadstart, then one or moreprogressevents, thenload, thenloadend. - Stopped early:
loadstart, thenabort, thenloadend. - Failed:
loadstart, thenerror, thenloadend.
loadstart, progress, load, then loadend. An aborted read fires abort instead, but it still ends with loadend.To read a whole file, the promise methods are simpler. To show a progress bar or let the user stop a read, use FileReader.
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 made-up Blob. None of your files are used.
FileReader sends progress events while it reads. The browser decides how big each piece is, so you may see one event or several. The Blob is made in this page, with about 4 million bytes.
Look at files from a picker or a drop
INTERACTIVEA page can get File objects in two ways:
- a file input,
<input type="file">, which opens a file picker, - a drop zone, an area of the page where you can drop files.
Once the user gives the page a file, the page can read its facts, read its bytes, or send it to a server later. Picking or dropping a file does not upload it.
const input = document.querySelector("input[type=file]");
input.addEventListener("change", () => {
const [file] = input.files;
console.log(file ? file.name : "no file selected");
});Line 1 finds the file input. Line 2 runs a function each time the user picks a file. Line 3 takes the first file from input.files. Line 4 prints its name, or no file selected if there is none.
input.files is a FileList, a list of File objects. The first file is at index 0. If the user cancels, the list is empty. The input's accept attribute tells the picker which files to show, but it is only a hint. Check the File yourself.
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 picked yet.
Pick a file or drop one here. Nothing is uploaded.
The inspector's code listens for two events on the drop zone:
dragoverfires while a file is dragged over the zone. Callingevent.preventDefault()here tells the browser this zone accepts drops.dropfires when the file is let go.event.dataTransfer.filesholds the dropped files.
Dropping a folder may not give an ordinary File, so the list can be empty. A good drop zone checks for that and doesn't crash.
const file = input.files[0];
if (file?.type.startsWith("image/")) {
preview.src = URL.createObjectURL(file);
console.log("image preview ready");
}Line 2 checks that a file was picked and that its type starts with image/. The ?. stops the check safely if there is no file. Line 3 gives the image an object URL, so it shows the picture. Line 4 prints image preview ready.
const maxBytes = 1_000_000;
const file = input.files[0];
const allowed = file?.type === "image/png" && file.size <= maxBytes;
console.log(allowed ? "ready to upload" : "reject before upload");Line 1 sets the largest size allowed: one million bytes. Line 3 allows the file only if it is a PNG image and not too big. Line 4 prints ready to upload or reject before upload. This check gives the user quick feedback. The server must still check the file again.
To send files to a server, use them with FormData. To keep files in the browser for later, compare the tools in Cache API and file storage.
Pick the right way to read
SORTEach file job needs something different. Ask what you need:
- all the text,
- the exact bytes,
- the bytes a piece at a time,
- a URL to show or download the file,
- a URL that holds the whole file as text, or
- progress events.
Then pick the tool that does that job.
Read each row of the table as a job, the tool to use, and the reason.
| What you want to do | Tool to use | Why it fits |
|---|---|---|
| Read a text file | await file.text() | It gives you all the text in one short line of code. |
| Work with the exact bytes | await file.arrayBuffer() | It gives you every byte, for files that aren't text. |
| Show a picture before upload | URL.createObjectURL(file) | The image loads from a short temporary URL, with no base64 copy. |
| Show progress or let the user stop | FileReader | It sends progress, abort, error, and loadend events. |
| Handle a huge file a piece at a time | blob.stream() | A stream gives you the bytes in chunks, one after another. |
blob.text()file.text()blob.arrayBuffer()blob.stream()URL.createObjectURL(blob)readAsDataURL(file)- FileReader progress events
- <a download> with an object URL
Put each tool under the job it does best.
A stream gives you the bytes in pieces, called chunks, one after another. That way the page doesn't need to hold a huge file in memory all at once.
const reader = blob.stream().getReader();
let chunks = 0;
for (;;) {
const { done, value } = await reader.read();
if (done) break;
chunks += 1;
console.log(value.byteLength);
}
console.log(chunks);Line 1 opens the Blob as a stream and gets a reader for it. Line 2 starts a chunk count at 0. The loop on line 3 runs until line 5 stops it. Line 4 waits for the next chunk. When there are no more chunks, done is true and line 5 leaves the loop. Otherwise, line 6 counts the chunk and line 7 prints its size in bytes. Line 9 prints how many chunks there were.
The Streams and progress lesson explains streams in more detail. For this lesson, remember the five choices: all the text, all the bytes, chunks, a preview URL, or events.
Where you will use this
PRACTICALYou use Files and Blobs whenever a page works with files without loading a new page. Some common jobs:
- Save data as a file: turn rows into CSV text, put it in a Blob, make an object URL, and give it to an
<a download>link. - Preview before upload: take a File from an input. Show a picture with an object URL, or show text with
file.text(). - Pictures made in the page: a canvas, the drawing area of a page, can give you its picture as a Blob. You can then preview it or let the user download it.
- Working offline: keep files in the browser's own storage, such as IndexedDB or the Origin Private File System, so the page can use them without the internet.
The browser never gives your page a full path like /Users/ada/taxes/receipt.pdf. It gives the file name and the bytes, and only after the user picks or drops the file. Browsers work this way on purpose, to keep the user's folders private.
Common mistakes
WATCH OUT- "Blob size is the string length." No. Blob size counts bytes. Letters with accents and emoji take more than one byte each.
- "The type proves what kind of file it is." No.
typeis only a label. It can be empty or wrong. - "Picking a file uploads it." No. The file is sent only if your code sends it, for example with FormData and
fetch. - "Object URLs go away by themselves right away." No. An object URL lasts until you cancel it or the page closes. Cancel previews you no longer need.
- "FileReader is the only way to read a file." No. The Blob methods, like
text(), are usually simpler. Use FileReader when you need its events. - "The browser shows the file's full path." No. It shows the file name only, not the folders it is in.
Practice exercises
5 EXERCISESPredict the two lines this code prints.
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 4 letters, so line 2 prints 4. The Blob holds 5 bytes, because é takes two, so line 3 prints 5.
Run the code in a browser console or in Node 22, and write down the three lines it prints.
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);Line 2 prints true, because a File is a Blob. Line 3 prints the name, hello.txt. Line 4 prints the size, 5 bytes.
Some clean-up code must run after every read, however it ends. Which FileReader event should it listen for?
loadend always comes last, whether the read worked, was stopped, or failed.
A page makes a new image preview each time the user picks a file. Which method should it call on the old preview's URL?
URL.revokeObjectURL(url);Cancel the old object URL when a new preview replaces it. Then the browser can free the memory.
Your page wants to show which folder a dropped file came from. Can JavaScript in the browser show the full path?
No. Show file.name, the size, the type, and lastModified. The page can't know the full path.
Check your understanding
8 QUESTIONSQuestion 1 of 8What does
Blob.sizecount?Choose an answer to see the explanation.
Question 2 of 8What does this code 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 code 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 cancel an object URL with
URL.revokeObjectURL?Choose an answer to see the explanation.
Question 6 of 8In what order does FileReader send events when a read works?
Choose an answer to see the explanation.
Question 7 of 8What is the best way to read all the text in a text file?
Choose an answer to see the explanation.
Question 8 of 8What must a drop zone do in its
dragoverlistener so it can get a drop?Choose an answer to see the explanation.
Key takeaways
- A Blob holds bytes and a type label. A File is a Blob with a name and a last-changed time.
- Blob size counts bytes, not letters. Letters with accents and emoji show the difference.
- Read with
text(),arrayBuffer(), andstream(). Use FileReader when you need progress, stopping, or data URLs. - Object URLs are short, temporary addresses for previews and downloads. Cancel them when you no longer need them.
- File inputs and drop zones never show full paths, and they never upload files by themselves.
Final definition.
Blob and File are browser objects that hold bytes. They let JavaScript read, preview, and download files that the user picked or that the page made.
Up next: Device & platform APIs.