Managing config values and secrets safely
Hardhat projects often need values that shouldn’t be committed to shared repositories, like private keys or API keys.
Hardhat 3 offers Configuration Variables to handle this in a secure way, either encrypted (using hardhat-keystore) or not, depending on what you need.
By default, Configuration Variables are read from environment variables, but plugins can define alternative ways to obtain their values.
This guide covers how to use Configuration Variables and the hardhat-keystore plugin to securely store sensitive values.
Using Configuration Variables
Section titled “Using Configuration Variables”A common example of a value that you don’t want to hardcode is an RPC URL that includes an API key, such as one from Alchemy or Infura:
import { defineConfig } from "hardhat/config";
export default defineConfig({3 collapsed lines
solidity: { version: "0.8.28", }, networks: { sepolia: { type: "http", url: "https://eth-sepolia.g.alchemy.com/v2/ABC123...", }, },});Instead of this, you can call the configVariable function to get the value at runtime:
import { configVariable, defineConfig } from "hardhat/config";
export default defineConfig({3 collapsed lines
solidity: { version: "0.8.28", }, networks: { sepolia: { type: "http", url: configVariable("SEPOLIA_RPC_URL"), }, },});In the snippet above, Hardhat will look for an environment variable named SEPOLIA_RPC_URL when it needs the value. You can define it inline when running a task:
SEPOLIA_RPC_URL='https://eth-sepolia.g.alchemy.com/v2/ABC123' npx hardhat run ./my-script.ts --network sepoliaSEPOLIA_RPC_URL='https://eth-sepolia.g.alchemy.com/v2/ABC123' pnpm hardhat run ./my-script.ts --network sepoliaSEPOLIA_RPC_URL='https://eth-sepolia.g.alchemy.com/v2/ABC123' yarn hardhat run ./my-script.ts --network sepoliaConfiguration Variables are lazy, meaning that they’re only resolved when actually needed. This allows you to use Hardhat without having to define all of them upfront.
Formatting Configuration Variables
Section titled “Formatting Configuration Variables”You can provide a second argument to configVariable called format. This is a template string where all instances of {variable} get replaced with the actual config variable value.
For example, instead of storing an entire URL, you can store just the API key part.
import { configVariable, defineConfig } from "hardhat/config";
export default defineConfig({3 collapsed lines
solidity: { version: "0.8.28", }, networks: { mainnet: { type: "http", url: configVariable( "ALCHEMY_API_KEY", "https://eth-mainnet.g.alchemy.com/v2/{variable}", ), }, sepolia: { type: "http", url: configVariable( "ALCHEMY_API_KEY", "https://eth-sepolia.g.alchemy.com/v2/{variable}", ), }, },});You can then run it like this:
ALCHEMY_API_KEY=foo npx hardhat run ./my-script.ts --network sepoliaALCHEMY_API_KEY=foo pnpm hardhat run ./my-script.ts --network sepoliaALCHEMY_API_KEY=foo yarn hardhat run ./my-script.ts --network sepoliaManaging secrets with the hardhat-keystore plugin
Section titled “Managing secrets with the hardhat-keystore plugin”Resolving Configuration Variables from environment variables isn’t safe (due to command history storage, among other things) nor convenient. The hardhat-keystore plugin lets you store sensitive values in an encrypted file and use them as Configuration Variables. This way, you don’t have to type them every time or commit them to disk in plain text, where they can be stolen.
If you are using a Hardhat Toolbox or created a sample project using Hardhat 3, you already have the plugin installed.
Otherwise, check out the hardhat-keystore documentation to install it.
Using the keystore
Section titled “Using the keystore”Use the keystore set task to store an encrypted secret:
npx hardhat keystore set SEPOLIA_RPC_URLpnpm hardhat keystore set SEPOLIA_RPC_URLyarn hardhat keystore set SEPOLIA_RPC_URLWhen you run this task for the first time, you’ll be prompted to create a password for your keystore. After that, you’ll need to enter that password to confirm the operation every time you add a new value. The secret is then stored encrypted in a file in your home directory.
Once a value is stored in the keystore, you can use it in your configuration:
import { configVariable, defineConfig } from "hardhat/config";
export default defineConfig({3 collapsed lines
solidity: { version: "0.8.28", }, networks: { sepolia: { type: "http", url: configVariable("SEPOLIA_RPC_URL"), }, },});Hardhat will prompt you to enter the password when the configuration variable is needed.
Managing encrypted variables
Section titled “Managing encrypted variables”To manage your keystore, the plugin provides the following self-explanatory tasks:
npx hardhat keystore listnpx hardhat keystore get <key>npx hardhat keystore delete <key>npx hardhat keystore change-passwordnpx hardhat keystore pathpnpm hardhat keystore listpnpm hardhat keystore get <key>pnpm hardhat keystore delete <key>pnpm hardhat keystore change-passwordpnpm hardhat keystore pathyarn hardhat keystore listyarn hardhat keystore get <key>yarn hardhat keystore delete <key>yarn hardhat keystore change-passwordyarn hardhat keystore pathImproving UX when using keystore values during development
Section titled “Improving UX when using keystore values during development”To avoid repeatedly writing the keystore password when working locally with values that aren’t sensitive, you can use the Development Keystore. This is a separate keystore that doesn’t require a password when accessing values, allowing you to keep the keystore workflow even when security isn’t critical.
To use it, just store the values with the --dev flag:
npx hardhat keystore set --dev <key>pnpm hardhat keystore set --dev <key>yarn hardhat keystore set --dev <key>Learn more
Section titled “Learn more”To learn more about how Configuration Variables work, you can read their explanation article.