#How to use configuration variables and the keystore plugin
Hardhat projects often need values that vary from one developer to another or that shouldn't be committed to source control, like private keys or RPC URLs with API keys. To manage this securely and flexibly, Hardhat supports configuration variables. This guide covers how to use them and how to securely store sensitive values with the hardhat-keystore
plugin.
# 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:
const config = {
networks: {
sepolia: {
url: "https://eth-sepolia.g.alchemy.com/v2/ABC123...",
// ...
},
},
};
To solve this, Hardhat lets you use configuration variables. These are placeholders that get replaced with actual values at runtime:
import { configVariable } from "hardhat/config";
const config = {
networks: {
sepolia: {
url: configVariable("SEPOLIA_RPC_URL"),
// ...
},
},
};
This way, the actual value is never committed to your repository.
By default, configuration variables get their values from environment variables. For example, 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 test
SEPOLIA_RPC_URL='https://eth-sepolia.g.alchemy.com/v2/ABC123...' pnpm hardhat test
But configuration variables are extensible: plugins can define alternative ways to obtain their values. This means you're not limited to environment variables—you can plug in other sources, such as encrypted storage, cloud secrets managers, or any custom logic.
Another important detail is that configuration variables are lazy, meaning that they're only resolved when actually needed. For example, if you define a network that uses a configuration variable for its RPC URL, and that variable isn't set, it won't cause any issues unless you actually try to connect to that network. Running tasks like compile
, or executing tests that don't use the network, will work just fine.
# The hardhat-keystore
plugin
In the previous section we said that configuration variables are resolved from environment variables by default. This works, but it comes with some downsides. For example, if you type secrets directly into your shell, they'll end up in your shell history. You also have to re-set them every time, or rely on tools like .env
files, which have their own limitations.
To address these issues, Hardhat provides an official plugin called hardhat-keystore
. It lets you store sensitive values in an encrypted file and use them as configuration variables, without having to type them every time or commit them to disk in plain text.
#Setup
The hardhat-keystore
plugin is part of the example project, but it can also be installed manually:
-
Install the plugin:
npmpnpmnpm install --save-dev @nomicfoundation/hardhat-keystore@next
pnpm install --save-dev @nomicfoundation/hardhat-keystore@next
-
Add it to the list of plugins in your Hardhat configuration:
import hardhatKeystore from "@nomicfoundation/hardhat-keystore"; const config: HardhatUserConfig = { plugins: [ hardhatKeystore, // ...other plugins... ], // ...other config... }; export default config;
#Using the keystore
To store an encrypted secret, use the keystore set
task. For example, to store an Alchemy API key under the name SEPOLIA_RPC_URL
, run:
npx hardhat keystore set SEPOLIA_RPC_URL
pnpm hardhat keystore set SEPOLIA_RPC_URL
When you run this task for the first time, you'll be prompted to create a password for your keystore. After that, every time you add a new value, you'll need to enter that password to confirm the operation. The secret is then encrypted in a file in your home directory.
Once a value is stored in the keystore, you can use it in your configuration just like any other configuration variable. For example, if you stored an RPC URL under the name SEPOLIA_RPC_URL
, you can reference it in exactly the same way we did above:
import { configVariable } from "hardhat/config";
const config = {
networks: {
sepolia: {
url: configVariable("SEPOLIA_RPC_URL"),
// ...
},
},
};
When the configuration variable is needed, Hardhat will prompt you to enter the password and decrypt the value from the keystore.
If the value isn't needed—because the task doesn't use it—you won't be prompted at all. This means you can define encrypted variables freely without affecting tasks like compile or tests that don't touch the network.
# Managing encrypted variables
Besides keystore set
, Hardhat provides several other tasks to help you manage your encrypted configuration variables.
-
To view the value of a stored variable, use the
keystore get
task:npmpnpmnpx hardhat keystore get SEPOLIA_RPC_URL
pnpm hardhat keystore get SEPOLIA_RPC_URL
-
To delete a configuration variable from the keystore, use
keystore delete
:npmpnpmnpx hardhat keystore delete SEPOLIA_RPC_URL
pnpm hardhat keystore delete SEPOLIA_RPC_URL
-
To list all stored variable names, use
keystore list
:npmpnpmnpx hardhat keystore list
pnpm hardhat keystore list
-
To find the path to the keystore file, use
keystore path
:npmpnpmnpx hardhat keystore path
pnpm hardhat keystore path
-
To change the keystore's password, use
keystore change-password
:npmpnpmnpx hardhat keystore change-password
pnpm hardhat keystore change-password