Academic Block

Upload & Preview (FileReader) Get Certified

The FileReader API lets you read the contents of a file selected by the user, without uploading it to a server first. This is how image previews, client-side validation, and local text processing work.

Getting a File from an Input

When a user selects a file, the browser gives you a File object that contains the name, size, type, and the ability to read its content. On a real page you get it from input.files[0]. In this lesson the examples use stand-ins (createMockFile() and createMockFileReader(), placed at the bottom of each example) because the tutorial editor has no file picker; the code you write around them is the same as with the real File and FileReader.

What this code does: Creates a stand-in File object and prints the three properties you can read as soon as a user picks a file: name, size in bytes and type (the MIME type, such as text/plain). The same details are then drawn as a small file card below the text output. Nothing has been read yet: at this point the browser only knows the file’s properties, not its content.

// On a real page the File comes from an <input type="file">:
//   const file = document.querySelector("#picker").files[0];
// Here we create a stand-in with the same name, size and type properties.
const file = createMockFile("notes.txt", "Hello from a local file!", "text/plain");

console.log("File name:", file.name);
console.log("File size:", file.size, "bytes");
console.log("File type:", file.type);

// Show the same information as a card below the text output
showPreview("What the user selected", makeFileCard(file));

// ===== Helpers below are not part of the lesson =====
function showPreview(label, node) {
    setTimeout(() => {
        const box = document.createElement("div");
        box.className = "demo-preview-box";
        box.innerHTML = "<p class='demo-preview-label'>" + label + "</p>";
        box.appendChild(node);
        document.getElementById("previewContent").appendChild(box);
    }, 0);
}

function makeFileCard(file) {
    const card = document.createElement("div");
    card.className = "demo-file-card";
    card.style.cssText = "border:1px solid #cbd5e1;border-radius:8px;padding:10px 14px;font:14px sans-serif;background:#f8fafc;color:#0f172a;display:inline-block;";
    card.innerHTML = "<strong>" + file.name + "</strong><br>" + file.size + " bytes<br>" + file.type;
    return card;
}

function createMockFile(name, content, type) {
    return {
        name: name,
        size: content.length,
        type: type || "text/plain",
        _content: content
    };
}

Reading a File as Text

readAsText() is the simplest way to get the content of a text file (or JSON/CSV). The result is available inside the onload handler.

What this code does: Creates a text file and a reader, attaches an onload handler, and only then calls readAsText(). When the read finishes, onload receives an event whose event.target.result holds the file’s text; the example prints it and shows it in a text viewer below the output. The order (handler first, read second) is the key idea, because a real read finishes later.

const file = createMockFile("story.txt", "Once upon a time in JavaScript...");
const reader = createMockFileReader(); // real page: new FileReader()

// 1) Say what to do when the read finishes
reader.onload = (event) => {
    console.log("File contents:");
    console.log(event.target.result);
    showPreview("Text file shown as text", makeTextViewer(event.target.result));
};

// 2) Start reading (the result arrives in onload)
reader.readAsText(file);

// ===== Helpers below are not part of the lesson =====
function showPreview(label, node) {
    setTimeout(() => {
        const box = document.createElement("div");
        box.className = "demo-preview-box";
        box.innerHTML = "<p class='demo-preview-label'>" + label + "</p>";
        box.appendChild(node);
        document.getElementById("previewContent").appendChild(box);
    }, 0);
}

function makeTextViewer(text) {
    const box = document.createElement("pre");
    box.className = "demo-text-viewer";
    box.style.cssText = "margin:0;padding:10px;border-radius:6px;background:#0f172a;color:#e2e8f0;font:14px monospace;white-space:pre-wrap;";
    box.textContent = text;
    return box;
}

function createMockFile(name, content, type) {
    return {
        name: name,
        size: content.length,
        type: type || "text/plain",
        _content: content
    };
}

function createMockFileReader() {
    const reader = {
        result: null,
        error: null,
        onload: null,
        onerror: null,
        onprogress: null,
        readAsText(file) {
            finishRead(file, () => file._content);
        },
        readAsDataURL(file) {
            finishRead(file, () => "data:" + file.type + ";base64," + btoa(file._content));
        },
        readAsArrayBuffer(file) {
            finishRead(file, () => Uint8Array.from(file._content, (c) => c.charCodeAt(0)).buffer);
        }
    };

    function finishRead(file, makeResult) {
        if (!file || file._unreadable) {
            reader.error = new Error("The file could not be read");
            if (reader.onerror) reader.onerror({ target: reader });
            return;
        }

        for (let step = 1; step <= 4; step++) {
            if (reader.onprogress) {
                reader.onprogress({ loaded: Math.round(file.size * step / 4), total: file.size });
            }
        }

        reader.result = makeResult();
        if (reader.onload) reader.onload({ target: reader });
    }

    return reader;
}

Reading as a Data URL (Image Preview)

readAsDataURL() converts a file into a base64 data URL. This is the standard way to show an image preview before uploading.

What this code does: Draws a small picture, turns it into the bytes of a real PNG file, reads that file with readAsDataURL() and shows the result as an actual image preview below the output. It prints the start of the data URL (the data:image/png;base64, prefix tells the browser what the bytes are) and compares its length with the file size: base64 makes the data about a third larger than the original bytes.

// Build a small real PNG so the preview is a genuine image
const picture = document.createElement("canvas");
picture.width = 160;
picture.height = 90;
const pctx = picture.getContext("2d");
pctx.fillStyle = "#4f46e5";
pctx.fillRect(0, 0, 160, 90);
pctx.fillStyle = "#facc15";
pctx.beginPath();
pctx.arc(80, 45, 28, 0, Math.PI * 2);
pctx.fill();

// Turn it into "file content" (a string of raw bytes)
const pngBase64 = picture.toDataURL("image/png").split(",")[1];
const file = createMockFile("photo.png", atob(pngBase64), "image/png");

const reader = createMockFileReader();

reader.onload = (event) => {
    const dataUrl = event.target.result;
    console.log("Data URL ready for preview:");
    console.log(dataUrl.slice(0, 50) + "...");
    console.log("Length:", dataUrl.length, "characters for a", file.size, "byte file");

    const img = document.createElement("img");
    img.className = "demo-image-preview";
    img.src = dataUrl;
    showPreview("Preview built from the data URL", img);
};

reader.readAsDataURL(file);

// ===== Helpers below are not part of the lesson =====
function showPreview(label, node) {
    setTimeout(() => {
        const box = document.createElement("div");
        box.className = "demo-preview-box";
        box.innerHTML = "<p class='demo-preview-label'>" + label + "</p>";
        box.appendChild(node);
        document.getElementById("previewContent").appendChild(box);
    }, 0);
}

function createMockFile(name, content, type) {
    return {
        name: name,
        size: content.length,
        type: type || "text/plain",
        _content: content
    };
}

function createMockFileReader() {
    const reader = {
        result: null,
        error: null,
        onload: null,
        onerror: null,
        onprogress: null,
        readAsText(file) {
            finishRead(file, () => file._content);
        },
        readAsDataURL(file) {
            finishRead(file, () => "data:" + file.type + ";base64," + btoa(file._content));
        },
        readAsArrayBuffer(file) {
            finishRead(file, () => Uint8Array.from(file._content, (c) => c.charCodeAt(0)).buffer);
        }
    };

    function finishRead(file, makeResult) {
        if (!file || file._unreadable) {
            reader.error = new Error("The file could not be read");
            if (reader.onerror) reader.onerror({ target: reader });
            return;
        }

        for (let step = 1; step <= 4; step++) {
            if (reader.onprogress) {
                reader.onprogress({ loaded: Math.round(file.size * step / 4), total: file.size });
            }
        }

        reader.result = makeResult();
        if (reader.onload) reader.onload({ target: reader });
    }

    return reader;
}

Reading a File as an ArrayBuffer

readAsArrayBuffer() gives you the raw bytes. Useful for binary formats, custom parsers, or WebAssembly.

What this code does: Reads a six-byte file with readAsArrayBuffer(), wraps the result in a Uint8Array so you can look at single bytes, and prints the length, the first three byte values and the characters they stand for (65, 66 and 67 are A, B and C). A grid below the text output shows every byte as a coloured cell with its number and character.

const file = createMockFile("data.bin", "ABC123");
const reader = createMockFileReader();

reader.onload = (event) => {
    const bytes = new Uint8Array(event.target.result);
    console.log("Byte length:", bytes.length);
    console.log("First three bytes:", bytes[0], bytes[1], bytes[2]);
    console.log("As characters:", String.fromCharCode(bytes[0], bytes[1], bytes[2]));
    showPreview("Every byte of the file", makeByteGrid(bytes));
};

reader.readAsArrayBuffer(file);

// ===== Helpers below are not part of the lesson =====
function showPreview(label, node) {
    setTimeout(() => {
        const box = document.createElement("div");
        box.className = "demo-preview-box";
        box.innerHTML = "<p class='demo-preview-label'>" + label + "</p>";
        box.appendChild(node);
        document.getElementById("previewContent").appendChild(box);
    }, 0);
}

function makeByteGrid(bytes) {
    const grid = document.createElement("div");
    grid.className = "demo-byte-grid";
    grid.style.cssText = "display:flex;flex-wrap:wrap;gap:4px;";
    bytes.forEach((value) => {
        const cell = document.createElement("div");
        cell.className = "demo-byte-cell";
        cell.style.cssText = "padding:6px 10px;border-radius:4px;font:12px monospace;color:#fff;background:hsl(" + value * 2 + ", 55%, 40%);";
        cell.textContent = value + " = " + String.fromCharCode(value);
        grid.appendChild(cell);
    });
    return grid;
}

function createMockFile(name, content, type) {
    return {
        name: name,
        size: content.length,
        type: type || "text/plain",
        _content: content
    };
}

function createMockFileReader() {
    const reader = {
        result: null,
        error: null,
        onload: null,
        onerror: null,
        onprogress: null,
        readAsText(file) {
            finishRead(file, () => file._content);
        },
        readAsDataURL(file) {
            finishRead(file, () => "data:" + file.type + ";base64," + btoa(file._content));
        },
        readAsArrayBuffer(file) {
            finishRead(file, () => Uint8Array.from(file._content, (c) => c.charCodeAt(0)).buffer);
        }
    };

    function finishRead(file, makeResult) {
        if (!file || file._unreadable) {
            reader.error = new Error("The file could not be read");
            if (reader.onerror) reader.onerror({ target: reader });
            return;
        }

        for (let step = 1; step <= 4; step++) {
            if (reader.onprogress) {
                reader.onprogress({ loaded: Math.round(file.size * step / 4), total: file.size });
            }
        }

        reader.result = makeResult();
        if (reader.onload) reader.onload({ target: reader });
    }

    return reader;
}

Handling Errors

Always attach an onerror handler. A file can fail to read if it was deleted or if the browser cannot access it.

What this code does: Defines tryRead(), which reads a file with both an onload and an onerror handler, and calls it twice: once with a normal file and once with a file marked as unreadable (like a file deleted or locked after it was chosen). The good file prints its content and shows a green badge; the bad one triggers onerror, prints the message stored in reader.error and shows a red badge.

function tryRead(file) {
    const reader = createMockFileReader();

    reader.onload = () => {
        console.log("Read OK:", file.name, "->", reader.result);
        showPreview(file.name, makeStatusBox(true, "Read successfully"));
    };

    reader.onerror = () => {
        console.log("Read failed:", file.name, "->", reader.error.message);
        showPreview(file.name, makeStatusBox(false, reader.error.message));
    };

    reader.readAsText(file);
}

const goodFile = createMockFile("notes.txt", "Meeting at 10:00");
const badFile = createMockFile("report.txt", "Quarterly numbers");
badFile._unreadable = true; // simulates a file deleted or locked after it was chosen

tryRead(goodFile);
tryRead(badFile);

// ===== Helpers below are not part of the lesson =====
function showPreview(label, node) {
    setTimeout(() => {
        const box = document.createElement("div");
        box.className = "demo-preview-box";
        box.innerHTML = "<p class='demo-preview-label'>" + label + "</p>";
        box.appendChild(node);
        document.getElementById("previewContent").appendChild(box);
    }, 0);
}

function makeStatusBox(ok, message) {
    const box = document.createElement("div");
    box.className = "demo-status-box";
    box.style.cssText = "padding:8px 12px;border-radius:6px;font:14px sans-serif;color:#fff;display:inline-block;background:" + (ok ? "#16a34a" : "#dc2626") + ";";
    box.textContent = (ok ? "✔ " : "✖ ") + message;
    return box;
}

function createMockFile(name, content, type) {
    return {
        name: name,
        size: content.length,
        type: type || "text/plain",
        _content: content
    };
}

function createMockFileReader() {
    const reader = {
        result: null,
        error: null,
        onload: null,
        onerror: null,
        onprogress: null,
        readAsText(file) {
            finishRead(file, () => file._content);
        },
        readAsDataURL(file) {
            finishRead(file, () => "data:" + file.type + ";base64," + btoa(file._content));
        },
        readAsArrayBuffer(file) {
            finishRead(file, () => Uint8Array.from(file._content, (c) => c.charCodeAt(0)).buffer);
        }
    };

    function finishRead(file, makeResult) {
        if (!file || file._unreadable) {
            reader.error = new Error("The file could not be read");
            if (reader.onerror) reader.onerror({ target: reader });
            return;
        }

        for (let step = 1; step <= 4; step++) {
            if (reader.onprogress) {
                reader.onprogress({ loaded: Math.round(file.size * step / 4), total: file.size });
            }
        }

        reader.result = makeResult();
        if (reader.onload) reader.onload({ target: reader });
    }

    return reader;
}

Tracking Read Progress

For larger files you can listen to the progress event to update a progress bar.

What this code does: Reads a longer text file and listens to the progress event. The reader reports progress four times; each report prints the bytes loaded and the percentage and draws a progress bar at that percentage. When all the data has arrived, onload prints the content. Real readers fire progress many times for big files, and the same percentage calculation can drive one progress bar that you update.

const file = createMockFile("big.txt", "Some longer file content here, long enough to report progress in several steps.");
const reader = createMockFileReader();

reader.onprogress = (event) => {
    const percent = Math.round((event.loaded / event.total) * 100);
    console.log("Loaded", event.loaded, "of", event.total, "bytes (" + percent + "%)");
    showPreview("Progress: " + percent + "%", makeProgressBar(percent));
};

reader.onload = () => {
    console.log("Done reading:", reader.result);
    showPreview("Finished", makeStatusBox(true, "Read complete: " + file.size + " bytes"));
};

reader.onerror = () => console.log("Error:", reader.error.message);

reader.readAsText(file);

// ===== Helpers below are not part of the lesson =====
function showPreview(label, node) {
    setTimeout(() => {
        const box = document.createElement("div");
        box.className = "demo-preview-box";
        box.innerHTML = "<p class='demo-preview-label'>" + label + "</p>";
        box.appendChild(node);
        document.getElementById("previewContent").appendChild(box);
    }, 0);
}

function makeProgressBar(percent) {
    const track = document.createElement("div");
    track.className = "demo-progress-track";
    track.style.cssText = "width:260px;max-width:100%;height:18px;background:#e2e8f0;border-radius:9px;overflow:hidden;";
    const fill = document.createElement("div");
    fill.className = "demo-progress-fill";
    fill.style.cssText = "height:100%;background:#4f46e5;width:" + percent + "%;";
    track.appendChild(fill);
    return track;
}

function makeStatusBox(ok, message) {
    const box = document.createElement("div");
    box.className = "demo-status-box";
    box.style.cssText = "padding:8px 12px;border-radius:6px;font:14px sans-serif;color:#fff;display:inline-block;background:" + (ok ? "#16a34a" : "#dc2626") + ";";
    box.textContent = (ok ? "✔ " : "✖ ") + message;
    return box;
}

function createMockFile(name, content, type) {
    return {
        name: name,
        size: content.length,
        type: type || "text/plain",
        _content: content
    };
}

function createMockFileReader() {
    const reader = {
        result: null,
        error: null,
        onload: null,
        onerror: null,
        onprogress: null,
        readAsText(file) {
            finishRead(file, () => file._content);
        },
        readAsDataURL(file) {
            finishRead(file, () => "data:" + file.type + ";base64," + btoa(file._content));
        },
        readAsArrayBuffer(file) {
            finishRead(file, () => Uint8Array.from(file._content, (c) => c.charCodeAt(0)).buffer);
        }
    };

    function finishRead(file, makeResult) {
        if (!file || file._unreadable) {
            reader.error = new Error("The file could not be read");
            if (reader.onerror) reader.onerror({ target: reader });
            return;
        }

        for (let step = 1; step <= 4; step++) {
            if (reader.onprogress) {
                reader.onprogress({ loaded: Math.round(file.size * step / 4), total: file.size });
            }
        }

        reader.result = makeResult();
        if (reader.onload) reader.onload({ target: reader });
    }

    return reader;
}

Choosing a Read Method

Method Result Type Typical Use Case
readAsText()StringText files, JSON, CSV
readAsDataURL()Base64 data URLImage previews (img.src)
readAsArrayBuffer()ArrayBufferBinary formats, custom parsing
file.text() / file.arrayBuffer()PromiseSame as above, with async/await

Common Mistakes

  • Reading reader.result immediately instead of waiting for onload
  • Forgetting that FileReader methods are always asynchronous
  • Using readAsText() on binary files (images, PDFs)
  • Not handling onerror
  • Starting a second read on the same reader while the first is still running (use one reader per file)
  • Using readAsDataURL() on very large files without considering the size increase

Best Practices

  • Always assign both onload and onerror
  • Use readAsDataURL() specifically for image previews
  • Use readAsArrayBuffer() for binary data you will parse yourself
  • Prefer the promise-based methods when you don’t need progress events
  • Validate file type and size before reading

Quick Summary

  • FileReader reads a selected file’s contents in the browser
  • readAsText(), readAsDataURL(), and readAsArrayBuffer() are the three main methods
  • The result is only available inside onload
  • readAsDataURL() is the standard technique for instant image previews
  • Modern code can often use file.text() / file.arrayBuffer() instead

Try It Yourself

What this code does: Reads three files: a text file that loads fine, a file whose type is not text/plain (skipped before reading) and a text file that fails to read. For each file the code validates the type, attaches progress, load and error handlers, then reads. The output shows a text viewer for the good file and red badges for the skipped and failed ones. Try changing a file’s type or content, or mark another file as _unreadable.

const files = [
    createMockFile("story.txt", "Once upon a time, in a JavaScript file...", "text/plain"),
    createMockFile("photo.png", "not really an image", "image/png"),
    createMockFile("broken.txt", "This one will fail", "text/plain")
];
files[2]._unreadable = true; // simulates a file that cannot be read

function readTextFile(file) {
    if (file.type !== "text/plain") {
        console.log("Skipped", file.name, "- not a text file (" + file.type + ")");
        showPreview(file.name, makeStatusBox(false, "Skipped: " + file.type));
        return;
    }

    const reader = createMockFileReader();

    reader.onprogress = (event) => {
        const percent = Math.round((event.loaded / event.total) * 100);
        console.log(file.name + ": " + percent + "%");
    };

    reader.onload = () => {
        console.log("Loaded", file.name + ":", reader.result);
        showPreview(file.name, makeTextViewer(reader.result));
    };

    reader.onerror = () => {
        console.log("Error reading", file.name + ":", reader.error.message);
        showPreview(file.name, makeStatusBox(false, reader.error.message));
    };

    reader.readAsText(file);
}

files.forEach(readTextFile);

// ===== Helpers below are not part of the lesson =====
function showPreview(label, node) {
    setTimeout(() => {
        const box = document.createElement("div");
        box.className = "demo-preview-box";
        box.innerHTML = "<p class='demo-preview-label'>" + label + "</p>";
        box.appendChild(node);
        document.getElementById("previewContent").appendChild(box);
    }, 0);
}

function makeTextViewer(text) {
    const box = document.createElement("pre");
    box.className = "demo-text-viewer";
    box.style.cssText = "margin:0;padding:10px;border-radius:6px;background:#0f172a;color:#e2e8f0;font:14px monospace;white-space:pre-wrap;";
    box.textContent = text;
    return box;
}

function makeStatusBox(ok, message) {
    const box = document.createElement("div");
    box.className = "demo-status-box";
    box.style.cssText = "padding:8px 12px;border-radius:6px;font:14px sans-serif;color:#fff;display:inline-block;background:" + (ok ? "#16a34a" : "#dc2626") + ";";
    box.textContent = (ok ? "✔ " : "✖ ") + message;
    return box;
}

function createMockFile(name, content, type) {
    return {
        name: name,
        size: content.length,
        type: type || "text/plain",
        _content: content
    };
}

function createMockFileReader() {
    const reader = {
        result: null,
        error: null,
        onload: null,
        onerror: null,
        onprogress: null,
        readAsText(file) {
            finishRead(file, () => file._content);
        },
        readAsDataURL(file) {
            finishRead(file, () => "data:" + file.type + ";base64," + btoa(file._content));
        },
        readAsArrayBuffer(file) {
            finishRead(file, () => Uint8Array.from(file._content, (c) => c.charCodeAt(0)).buffer);
        }
    };

    function finishRead(file, makeResult) {
        if (!file || file._unreadable) {
            reader.error = new Error("The file could not be read");
            if (reader.onerror) reader.onerror({ target: reader });
            return;
        }

        for (let step = 1; step <= 4; step++) {
            if (reader.onprogress) {
                reader.onprogress({ loaded: Math.round(file.size * step / 4), total: file.size });
            }
        }

        reader.result = makeResult();
        if (reader.onload) reader.onload({ target: reader });
    }

    return reader;
}
Tip: Reach for readAsDataURL() for image previews, readAsArrayBuffer() for binary data, and the promise-based methods whenever you don’t need progress reporting, always with an error handler.

🧪 Test Your JavaScript Code

Edit the code on the left and click “Run Code” to see the console output on the right.

📝 JavaScript Code
👁 Console Output

Click “Run Code” to see the output here.

Ctrl + Enter to run code Esc to close