The other day I was deploying a web app to Vercel and couldn't help but notice how great their UX is when it comes to adding environment variables from .env
files.
The key and value inputs appear standard. However, there's an additional feature: you can paste the contents of an .env
file into the "key" field, and it will intelligently parse it and create new environment variable rows for every key and value pair you pasted. The video below shows how this looks in practice.
Environment variable management in the Vercel UI
To demonstrate the portability of Remix and my starter kit for it, I also deployed the same app to various other hosting providers and noticed that none of them handled environment variables anywhere near as smoothly as Vercel. I couldn't help but wonder why.
How hard could it be? Let's find out.
If you just want the solution, check out the code here. The key insight is to use onPaste()
instead of onChange()
to receive .env
contents in raw format.
Visually, all the environment variable management UIs I tried were some variation of this. This is acceptable UX as ultimately it does its job in accepting key and value pairs. Try pasting in this .env
file and you'll see where this falls flat.
API_KEY=my-secret APP_NAME=Demo USERNAME=@brianbriscoe_
My initial naive implementation of this was thinking I could just listen to the onChange
handler of the "key" input, determine if it's a valid .env
file, then parse the contents
const handleInputChange = (value) => { if (value.includes('\n')) { const parsedVariables = parseEnvContent(value); // append input elements } };
This did not work at all. By the time React receives the input from the onChange
handler, the browser has already stripped away the newline characters. What this means is that my .env
file that I copied like this
API_KEY=my-secret APP_NAME=Demo USERNAME=@brianbriscoe_
Gets pasted like this
API_KEY=my-secret APP_NAME=Demo USERNAME=@brianbriscoe_
This is actually the expected behaviour of <input />
elements and in fact the only HTML input that preserves newlines is <textarea />
, but Vercel don't use them here so neither will I.
Since I can't split the input contents by the newline character, the next most obvious delimiter, given the format of the input above, is the space " "
character. This time, I'll validate the input's content as a .env
by splitting the content by the space character and ensure each value has an equals sign "="
const isEnvFormat = (content) => { const pairs = content.split(' '); return pairs.length > 1 && pairs.every(pair => pair.includes('=')); };
This works! But what happens when I change my APP_NAME
from "Demo"
to "My Demo App"
? By treating the content as space-separated key and value pairs, I'm assuming the pairs themselves don't include any space characters. This is a fair assumption for the key side of the equals sign but not the value side.
It was obvious now the most robust solution would require parsing the input in its truest form: from the user's clipboard.
Thankfully, I'm in luck as native <input />
elements support exactly this with the onPaste()
handler. Intercepting the value in the paste handler completely negates the issues from before as the .env
contents are received in raw format.
const handlePaste = (e) => { const pastedText = e.clipboardData.getData('text'); if (isEnvFormat(pastedText)) { e.preventDefault(); const parsedVariables = parseEnvContent(pastedText); // append input elements } else { // handle paste normally } } return <input onPaste={handlePaste} placeholder={'e.g. API_KEY'} />
With a couple of other tweaks like trimming single- or double-quoted values, handling duplicate keys, and a fancy regex for isEnvFormat()
, I think it's pretty much there.
If you've got this far, I assume you're invested enough to paste this .env
file into the form below.
API_KEY=my-secret APP_NAME="My Demo App" USERNAME=@brianbriscoe_
My main takeaway from this is there's no magic involved in this functionality. The bulk of the work is handled by the native onPaste()
method and the rest can be implemented using basically as it doesn't depend on any specific framework, library or even language. That being said, the React implementation from this article can be found here. If you have any influence in an organization that handles environment variables via a UI like this, I urge you to consider adding this functionality.
Thanks for reading! If you found this interesting, please share or give me a follow on X for more content like this.