As a web developer, you’ve likely built a feature that allows users to upload an image. It’s a common task: you add an <input type="file"> element, and with a bit of code, a user can select a high-resolution, 15 MB photo from their device and send it to your server. It works seamlessly.
So, you decide to add a more modern feature: allowing users to take a photo directly within the app using their device's camera. You use the camera API to capture an image at the same high resolution. But when you try to upload it, the process fails, hangs, or forces you to implement a complex solution like "chunking."
Why does this happen? Why can a web app handle a 15 MB file selection with ease, but choke on a 15 MB in-app camera capture? The answer lies in the two fundamentally different paths the data takes to get from the user to your server.
Think of the standard file upload process as hiring a professional moving company. They have the right tools and a direct, efficient process for moving a large object from your house to its destination.
When a user clicks <input type="file"> and selects an image, a similar process unfolds behind the scenes:
The Browser Takes Over: The moment the user selects a file, the browser's own native, highly-optimized code (written in languages like C++) takes control.
A Direct Binary Stream: The browser reads the file's raw binary data directly from the device's storage. It doesn't need to "understand" the file; it just needs to move it.
Efficient Transmission: This binary data is streamed directly to the server, typically as part of a multipart/form-data request.
The most important takeaway here is that your web app's JavaScript code barely touches the data. It never has to load the entire 15 MB file into the computer's active memory. The browser handles the heavy lifting efficiently and in the background. It’s a simple, direct, and robust operation.
Now, let's consider the in-app camera capture. This process is less like a moving company and more like trying to describe a large object over the phone so someone else can rebuild it. It’s an indirect, multi-step process managed almost entirely by your JavaScript code.
Receiving a Live Stream: When your app accesses the camera, it doesn't get a "file." It gets a live video stream via the getUserMedia API—a continuous flow of pixel data.
Capturing to a Canvas: To "take a photo," you grab a single frame from that live stream and draw it onto an HTML <canvas> element. At this moment, the image exists only as a collection of pixels in the browser's memory.
The Critical Conversion: To send this image, you must convert the pixel data into a format that can be transmitted. The standard web method is canvas.toDataURL(), which encodes the image into a Base64 string.
The Base64 Bottleneck: This is where the core problem arises. Base64 is a text-based representation of binary data. This conversion has two major consequences:
Increased Size: It inflates the data size by about 33%. Your 15 MB image is now a 20+ MB string of text.
Memory Overload: This entire 20+ MB string must now be held in the browser's active memory by your script.
Hitting the API Limit: When you try to send this data to your server using a standard API call (like fetch() or, in our Google Apps Script example, google.script.run), you run into a hard wall. These functions have payload size limits. You cannot simply pass a 20 MB string as an argument; the request is too large and will be rejected or time out.
Because the camera capture path ends with a single, massive string of text that exceeds API limits, we are forced to invent our own delivery system.
Chunking is the process of taking that giant Base64 string and using JavaScript to manually slice it into a series of smaller, manageable pieces. Each "chunk" is small enough to be sent in a separate, successful API call.
The app then sends these chunks one by one, and the server-side code has the new responsibility of catching each chunk, storing it temporarily, and reassembling all the pieces in the correct order to reconstruct the complete image file.
Understanding this distinction is crucial for any developer working with media on the web. It clarifies why two seemingly identical outcomes—uploading a 15 MB image—require vastly different strategies, and it reveals the hidden complexities of handling data within the browser environment.