Implementing Vercel-style paste functionality in your web app

Wednesday 21 August 2024

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.

The state of play

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_

First draft

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.

Round two

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.

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_

Wrapping up

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.