Reading and Writing Files in the Browser

2025-04-12

One thing I miss from recent jobs is working with browser APIs. In large enterprise React applications most frontend problems tend to be solved the React way and the browser platform tends to be ignored. But the reality is that the browser APIs are in constant development and not being aware of what you can do with them can often make you add unnecessary complexity to your application. Working on side-projects is a great opportunity to learn about them.

I’m building a little browser-based image editing tool for my mum. I’ll get into the details in another post, but basically I want to be able to upload an image, put it on a canvas, add text from data from a CSV file and download a copy of the image for each row in the CSV file.

My first instinct was to do the image generation server side (I learned about this recently), upload the images to a storage service and give the user a download link. This is all doable, but it feels too complicated for a side project with one user. All the image editing capabilities exist in the browser, so maybe I can deal with the file creation there too?

I had a vague memory that there were new file system APIs (probably from a random Syntax.fm episode) and after a quick search, I was correct. This article on Chrome for developers does a great job on explaining how the File System Access API works. But reading is not enough to learn about something, so here are the 2 little examples I wrote.

Reading a file

You can open a system file picker, read the contents of a file and render them into an element:

<button onclick="openFile()">Open</button>
<textarea id="editor"></textarea>

<script>
	const editor = document.getElementById("editor");
	const fileAccessSupported = "showOpenFilePicker" in self
	
	async function openFile() {
		if (!fileAccessSupported) {
			alert("File picker not supported in this browser.");
			return;
		}
		const [fileHandle] = await window.showOpenFilePicker();
		const file = await fileHandle.getFile();
		const content = await file.text();
		editor.value = content;
	}
</script>

Reading the text content of a file is trivial. For images it’s a bit more complicated: to draw an image on the canvas I need an instance of HTMLImageElement and to create one of those I need a URL to put in the src attribute. I would have to use the URL.createObjectUrl() method, passing it the image file (which is of type File, which is a Blob, which is what the method expects).

Writing a file

I made a little canvas that you can draw circles in by clicking on it. The button allows you to save the contents of the canvas as an image.

<button onclick="saveAs()">Save As</button>
<canvas width="400" height="300"></canvas>

<script>
const canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d");

async function saveAs() {
	const fileHandle = await window.showSaveFilePicker({
		suggestedName: "canvas_image.png",
		types: [
			{
				description: "Images",
				accept: {
					"image/*": [".png", ".gif", ".jpeg", ".webp"],
				},
			},
		],
	});
	
	const blob = await new Promise((resolve) => {
		canvas.toBlob((blob) => {
			resolve(blob);
		}, "image/png");
	});
	const writable = await fileHandle.createWritable();
	await writable.write(blob);
	await writable.close();
}

canvas.addEventListener("click", (e) => {
	const rect = canvas.getBoundingClientRect();
	const x = e.clientX - rect.left;
	const y = e.clientY - rect.top;
	
	ctx.fillStyle = "red";
	ctx.beginPath();
	ctx.ellipse(x, y, 10, 10, 0, 0, 2 * Math.PI);
	ctx.fill();
});
</script>

You can check out the code and try it for yourself on this Glitch.

It remains to be seen if I can save many files programatically, but it’s getting late and I’ve done enough learning for today 🥱

Final Thoughts

Every time I learn about a new browser API I think: “Why do we even bother with all these JS frameworks?”. Obviously there are limitations to this API and it’s not supported by all browsers, but it fits this side-project perfectly and it saved me from writing a bunch of complicated code and the users of this tool a couple of roundtrips to serverland. Thanks browser, you’re the best ♥️