When I first started to put this demo together, I was operating under the assumption that all Data URIs had to be Base64-encoded. As such, I started to look at how one might convert plain text into a Base64 payload. It was then that I discovered that modern browsers provide btoa() and atob() methods specifically for Base64 encoding and decoding, respectively.

But then, when I went to double-check the Data URI syntax on MDN, I saw that they had examples that completely omitted the "base64" directive. As it turns out, you only need to Base64-encode non-text payloads, like images. If you need a Data URI that points to a plain text payload, all you have to do is encode the value the same way that you would encode any other URI component.

To pull both of these new findings together and cement them in my mental model, I created a small demo in which you can enter arbitrary text into a Textarea form field. The entered text value can then be downloaded as a .txt file using an anchor tag's href and download attributes. And, to make things more interesting, I added a checkbox that determines whether or not the Data URI in the href attribute is generated using a Base64 or a plain-text encoding.

NOTE: As a reminder, the "download" attribute of the Anchor instructs the browser to download the associated URL instead of navigating to it. It is supported in all modern browsers (including Edge).

As you can see, there's not too much going on in this demo. You enter some text, you click the anchor tag, you get prompted to download the text file (all on the client-side). It's definitely cool; but, the most interesting part of this demo is the updateDownloadHref() function that actually generates the Data URI for the download. Depending on the state of the checkbox, it either generates a Base64-encoded payload or a plain-text payload.

If we open the browser, enter some text in the textarea, and click the "Download Text" link, the browser will prompt us to save the file. And, if we open the file, we can see that it contains the text we entered in the browser:

As you can see, the "Download Text" link caused my Chrome browser to download the "data.txt" file. And, when I open the .txt file in Sublime Text, the contents of the file mirror what we had in the Textarea input field.

Anyway, I suppose this was more of a "note to self" than anything else. I didn't realize that browsers offer native Base64 encoding and decoding support. And, I didn't realize that you could include non-Base64-encoded payloads in a Data URI. So, putting all of this new information together makes for a rather easy way to prompt the user to download custom text files in JavaScript without any server-side interaction required.

Reader Comments

Thanks so much for this article! This technique immediately solved a
major problem I was having with a tool at Lottery Post.

It is a lottery combinations generator that generates all possible
lottery combinations given a set of user-defined parameters, using a
JavaScript Web Worker. (Available here: https://www.lotterypost.com/combinations)
Previously the tool could not generate huge combination sets (several
million combinations) because it had to display them all on the page,
and the browser would run out of memory.

Now, using this technique, for any set of combinations greater than
100,000 lines, it shows a download button instead.

Because of the size of the downloads, I had to use a Blob technique
instead of using a data URL directly. (A data URL can not handle huge
sizes.) So the data URL is converted into a Blob object, and then the
Blob is converted into a URL.

I am so grateful that you documented this, I had no idea this
technique existed!

I'm glad you found this helpful :D And, I have to say, that I'm
actually very interested in your "Blob" approach. Someone on
my team just mentioned to me that the Data URI has a limitation (as
you mentioned). And that they were using the Blob approach. It's
something I'll have to take a look at as well.

Comment Etiquette: Please do not post spam. Please keep the comments on-topic.
Please do not post unrelated questions or large chunks of code. And, above
all, please be nice to each other - we're trying to have a good conversation here.

I am the co-founder and lead engineer at InVision App, Inc — the world's leading prototyping,
collaboration & workflow platform. I also rock out in JavaScript and ColdFusion 24x7 and I dream about
promise resolving asynchronously.