claudeers.
// Uncategorized / Others

dotenvx

a secure dotenv—from the creator of `dotenv`

// Uncategorized / Others[ cli ][ api ][ desktop ][ web ][ mobile ][ claude ]#claude#cli#configuration-file#curl#dotenv#dotenvx#end-to-end-encryption#env#uncategorized◷ BSD-3-Clause$open-sourceupdated 1 day ago

Install with your AI

Paste into Claude Code, Cursor, or any agent — it reads the repo and wires the tool into your project.

Install and set up dotenvx (npm project) into my current project.
Found on https://claudeers.com/dotenvx
Repo: https://github.com/dotenvx/dotenvx
Homepage/docs: https://dotenvx.com/docs
Detected install method: npm → npm install @dotenvx/dotenvx
Category: uncategorized. Platforms: cli, api, desktop, web, mobile.
Read the repo's README for exact setup and env vars, then install it and wire it into my project.

Claudeers Health Verdict:
unknown; community-verified: false. Confirm the source before running anything.
// or install directly (npm)
npm install @dotenvx/dotenvx
// or clone
git clone https://github.com/dotenvx/dotenvx

// compatibility

Platformscli, api, desktop, web, mobile
Operating systems—
AI compatibilityclaude
LicenseBSD-3-Clause
Pricingopen-source
LanguageJavaScript

Get your FREE $2.50 API credits to access TickAtlas financial data ↗

dotenvx

A secure dotenv–from the creator of dotenv.

  • Encrypt
  • Commit
  • Ship

Install · Quickstart

 

Quickstart

Install and use it in code just like dotenv.

npm install @dotenvx/dotenvx --save
// index.js
require('@dotenvx/dotenvx').config()
// or import '@dotenvx/dotenvx/config' // for esm

console.log(`Hello ${process.env.HELLO}`)

 

or install globally - unlocks dotenv for any language, framework, or platform!

with npm 🌍
npm i -g @dotenvx/dotenvx
dotenvx encrypt

 

with curl 🌐
curl -sfS https://dotenvx.sh | sh
dotenvx encrypt

 

with brew 🍺
brew tap dotenvx/brew
brew trust dotenvx/brew
brew install dotenvx
dotenvx encrypt

 

with docker 🐳
docker run -it --rm -v $(pwd):/app dotenv/dotenvx encrypt

 

with github releases 🐙
curl -L -o dotenvx.tar.gz "https://github.com/dotenvx/dotenvx/releases/latest/download/dotenvx-$(uname -s)-$(uname -m).tar.gz"
tar -xzf dotenvx.tar.gz
./dotenvx encrypt

 

or windows 🪟
winget install dotenvx
dotenvx encrypt

 

Usage

Encrypt your secrets in .env files. The values become ciphertext and only your private key can unlock them.

$ dotenvx encrypt
◈ encrypted (.env)

Commit your encrypted .env files with your code. It's safe. Now you can securely share secrets through git.

$ git add .env
$ git commit -m "encrypt .env"

Ship your code and secrets together. Dotenvx uses your private key to decrypt and inject your secrets just-in-time to your code.

$ dotenvx run -- node index.js
⟐ injected env (2) from .env

 

Design

Three widely used and proven primitives make up Dotenvx's design. Git, .env, and secp256k1.

We chose git because it is the best way to deliver digital goods. It is the container ship of the digital world. All code travels on its ships. Why not secrets? Why invent an inferior secrets delivery mechanism when the world class one is right there at your fingertips.

We chose .env because it is the open standard for loading secrets into code. It represents environment variables, the core primitive that all secrets end up injected into at runtime of code.

Lastly, we needed to choose an encryption algorithm. Encryption of the values inside the .env file would allow us to place those values with code, leveraging git's distribution advantages while still following the twelve-factor config – by separating the decryption key from the code (environment).

It was important that we choose a proven, simple, asymmetric standard with small keys. We chose secp256k1, battle-tested by Bitcoin for more than seventeen years. We made cryptography an opinionated choice so developers wouldn't have to. Encryption just worked.

The result is a system built entirely from primitives, developers already understand, and infrastructure they already use. Secrets can travel with code without becoming part of the code. Git distributes them, .env defines how apps consume them, and secp256k1 keeps their values unreadable. The kicker, all this works by setting a single private key on your infrastructure - no more risky async coordination of secrets, no more centralized downtime risk. Your secrets are always just there with your code, ready to be unlocked at runtime.

Read the whitepaper →

 

Run Anywhere

$ echo "HELLO=World" > .env
$ echo "console.log('Hello ' + process.env.HELLO)" > index.js

$ node index.js
Hello undefined # without dotenvx

$ dotenvx run -- node index.js
Hello World # with dotenvx
> :-D

see quickstart guides

More examples

Claude 🤖

Run Claude with your real secrets while redacting them from its output.

Prerequisite: install Claude Code to get the claude command.

$ curl -fsSL https://claude.ai/install.sh | bash
$ claude --version
$ echo "HELLO=World" > .env

$ dotenvx spec
$ dotenvx run -- claude -p 'Run `dotenvx get HELLO` and echo back just Hello VALUE' --dangerously-skip-permissions
Hello [REDACTED]

see Claude redaction guide

Codex ✨

Run Codex with your real secrets while redacting them from its output.

Prerequisite: install the Codex CLI to get the codex command.

$ npm install -g @openai/codex
$ codex --version
$ echo "HELLO=World" > .env

$ dotenvx spec
$ dotenvx run -- codex exec 'Run `dotenvx get HELLO` and echo back just Hello VALUE' --skip-git-repo-check
Hello [REDACTED]

see Codex redaction guide

Cursor 🖱️

Run Cursor with your real secrets while redacting them from its output.

Prerequisite: install the separate Cursor CLI. Installing the Cursor desktop app does not necessarily install the agent command.

$ curl https://cursor.com/install -fsS | bash
$ agent --version
$ echo "HELLO=World" > .env

$ dotenvx spec
$ dotenvx run -- agent -p --force 'Run `dotenvx get HELLO` and echo back just Hello VALUE' --output-format text
Hello [REDACTED]

see Cursor redaction guide

1Password 🔐

Run with secrets resolved directly from 1Password.

$ echo "HELLO=op://Personal/hello/password" > .env
$ dotenvx run -- sh -c 'echo Hello $HELLO'
Hello World

see 1Password guide

Bitwarden 🛡️

Run with secrets resolved directly from Bitwarden Password Manager.

$ echo 'HELLO="bw://My Hello Login/password"' > .env
$ dotenvx run -- sh -c 'echo Hello $HELLO'
Hello World

Install the Bitwarden Password Manager CLI before running dotenvx.

TypeScript 📘
// package.json
{
  "type": "module",
  "dependencies": {
    "chalk": "^5.3.0"
  }
}
// index.ts
import chalk from 'chalk'
console.log(chalk.blue(`Hello ${process.env.HELLO}`))
$ npm install
$ echo "HELLO=World" > .env

$ dotenvx run -- npx tsx index.ts
Hello World
Astro 🚀

Preface Astro scripts with dotenvx run -- and read your env values in Astro.

{
  "scripts": {
    "dev": "dotenvx run -- astro dev",
    "build": "dotenvx run -- astro build",
    "preview": "dotenvx run -- astro preview"
  }
}
export async function GET() {
  return new Response(
    JSON.stringify({
      HELLO: process.env.HELLO,
    }),
    {
      status: 200,
      headers: {
        "Content-Type": "application/json",
      },
    }
  );
}

see astro guide

Expo 🧭

Preface Expo scripts with dotenvx run --.

{
  "scripts": {
    "start": "dotenvx run -- expo start",
    "reset-project": "node ./scripts/reset-project.js",
    "android": "dotenvx run -- expo start --android",
    "ios": "dotenvx run -- expo start --ios",
    "web": "dotenvx run -- expo start --web",
    "lint": "expo lint"
  }
}

see expo guide

Next.js ▲

Install Dotenvx and @dotenvx/next-env.

$ npm install @dotenvx/dotenvx
$ npm install @dotenvx/next-env

Override @next/env in your package.json.

{
  "overrides": {
    "@next/env": "npm:@dotenvx/next-env"
  }
}

Encrypt your .env file.

$ npx dotenvx encrypt
◈ encrypted (.env)

Your encrypted secrets are automatically injected and readable in Next.js.

import { NextResponse } from 'next/server'

export async function GET() {
  return NextResponse.json({
    HELLO: process.env.HELLO
  })
}

Set DOTENV_PRIVATE_KEY in production before deploying.

Cloudflare Workers ⛅️
$ dotenvx encrypt -f .env.txt
// src/index.js
import envSrc from '../.env.txt'
import dotenvx from '@dotenvx/dotenvx'

const config = dotenvx.config({ envs: [{ type: 'env', value: envSrc, privateKeyName: 'DOTENV_PRIVATE_KEY' }] })
const envx = config.parsed

export default {
  async fetch(request, env, ctx) {
    return new Response(`Hello ${envx.HELLO}`)
  }
}
"scripts": {
  "deploy": "wrangler deploy",
  "dev": "wrangler dev --var $(dotenvx keypair -f .env.txt --format=colon)",
  "start": "wrangler dev --var $(dotenvx keypair -f .env.txt --format=colon)",
}
Bun 🥟
$ echo "HELLO=Test" > .env.test
$ echo "console.log('Hello ' + process.env.HELLO)" > index.js

$ bun index.js
Hello undefined

$ dotenvx run -f .env.test -- bun index.js
Hello Test
Deno 🦕
$ echo "HELLO=World" > .env
$ echo "console.log('Hello ' + Deno.env.get('HELLO'))" > index.ts

$ deno run --allow-env index.ts
Hello undefined

$ dotenvx run -- deno run --allow-env index.ts
Hello World

[!WARNING] Some of you are attempting to use the npm module directly with deno run. Don't, because deno currently has incomplete support for these encryption ciphers.

$ deno run -A npm:@dotenvx/dotenvx encrypt
Unknown cipher

Instead, use dotenvx as designed, by installing the cli as a binary - via curl, brew, etc.

Python 🐍
$ echo "HELLO=World" > .env
$ echo 'import os;print("Hello " + os.getenv("HELLO", ""))' > index.py

$ dotenvx run -- python3 index.py
Hello World

see extended python guide

PHP 🐘
$ echo "HELLO=World" > .env
$ echo '<?php echo "Hello {$_SERVER["HELLO"]}\n";' > index.php

$ dotenvx run -- php index.php
Hello World

see extended php guide

Ruby 💎
$ echo "HELLO=World" > .env
$ echo 'puts "Hello #{ENV["HELLO"]}"' > index.rb

$ dotenvx run -- ruby index.rb
Hello World

see extended ruby guide

Go 🐹
$ echo "HELLO=World" > .env
$ echo 'package main; import ("fmt"; "os"); func main() { fmt.Printf("Hello %s\n", os.Getenv("HELLO")) }' > main.go

$ dotenvx run -- go run main.go
Hello World

see extended go guide

Rust 🦀
$ echo "HELLO=World" > .env
$ echo 'fn main() {let hello = std::env::var("HELLO").unwrap_or("".to_string());println!("Hello {hello}");}' > src/main.rs

$ dotenvx run -- cargo run
Hello World

see extended rust guide

Java ☕️
$ echo "HELLO=World" > .env
$ echo 'public class Index { public static void main(String[] args) { System.out.println("Hello " + System.getenv("HELLO")); } }' > index.java

$ dotenvx run -- java index.java
Hello World
Clojure 🌿
$ echo "HELLO=World" > .env
$ echo '(println "Hello" (System/getenv "HELLO"))' > index.clj

$ dotenvx run -- clojure -M index.clj
Hello World
Kotlin 📐
$ echo "HELLO=World" > .env
$ echo 'fun main() { val hello = System.getenv("HELLO") ?: ""; println("Hello $hello") }' > index.kt
$ kotlinc index.kt -include-runtime -d index.jar

$ dotenvx run -- java -jar index.jar
Hello World
.NET 🔵
$ dotnet new console -n HelloWorld -o HelloWorld
$ cd HelloWorld
$ echo "HELLO=World" | Out-File -FilePath .env -Encoding utf8
$ echo 'Console.WriteLine($"Hello {Environment.GetEnvironmentVariable("HELLO")}");' > Program.cs

$ dotenvx run -- dotnet run
Hello World
Bash 🖥️
$ echo "HELLO=World" > .env

$ dotenvx run --quiet -- sh -c 'echo Hello $HELLO'
Hello World
Frameworks ▲
$ dotenvx run -- next dev
$ dotenvx run -- npm start
$ dotenvx run -- bin/rails s
$ dotenvx run -- php artisan serve

see framework guides

Docker 🐳
$ docker run -it --rm -v $(pwd):/app dotenv/dotenvx run -- node index.js

Or in any image:

FROM node:latest
RUN echo "HELLO=World" > .env && echo "console.log('Hello ' + process.env.HELLO)" > index.js
RUN curl -fsS https://dotenvx.sh/install.sh | sh
CMD ["/usr/local/bin/dotenvx", "run", "--", "echo", "Hello $HELLO"]

see docker guide

CI/CDs 🐙
name: build
on: [push]
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v3
    - uses: actions/setup-node@v3
      with:
        node-version: 16
    - run: curl -fsS https://dotenvx.sh/install.sh | sh
    - run: dotenvx run -- node build.js
      env:
        DOTENV_KEY: ${{ secrets.DOTENV_KEY }}

see github actions guide

Platforms
# heroku
heroku buildpacks:add https://github.com/dotenvx/heroku-buildpack-dotenvx

# docker
RUN curl -fsS https://dotenvx.sh | sh

# vercel
npm install @dotenvx/dotenvx --save

see platform guides

Process Managers
// pm2
"scripts": {
  "start": "dotenvx run -- pm2-runtime start ecosystem.config.js --env production"
},

see process manager guides

npx
# alternatively use npx
$ npx @dotenvx/dotenvx run -- node index.js
$ npx @dotenvx/dotenvx run -- next dev
$ npx @dotenvx/dotenvx run -- npm start
npm
$ npm install @dotenvx/dotenvx --save
{
  "scripts": {
    "start": "./node_modules/.bin/dotenvx run -- node index.js"
  },
  "dependencies": {
    "@dotenvx/dotenvx": "^0.5.0"
  }
}
$ npm run start

> start
> ./node_modules/.bin/dotenvx run -- node index.js

[[email protected]] injecting env (1) from .env.production
Hello World
Variable Expansion

Reference and expand variables already on your machine for use in your .env file.

# .env
USERNAME="username"
DATABASE_URL="postgres://${USERNAME}@localhost/my_database"
// index.js
console.log('DATABASE_URL', process.env.DATABASE_URL)
$ dotenvx run --debug -- node index.js
[[email protected]] injecting env (2) from .env
DATABASE_URL postgres://username@localhost/my_database
Command Substitution

Add the output of a command to one of your variables in your .env file.

# .env
DATABASE_URL="postgres://$(whoami)@localhost/my_database"
// index.js
console.log('DATABASE_URL', process.env.DATABASE_URL)
$ dotenvx run --debug -- node index.js
[[email protected]] injecting env (1) from .env
DATABASE_URL postgres://yourusername@localhost/my_database

 

Multiple Environments

Create a .env.production file and use -f to load it. It's straightforward, yet flexible.

$ echo "HELLO=production" > .env.production
$ echo "console.log('Hello ' + process.env.HELLO)" > index.js

$ dotenvx run -f .env.production -- node index.js
[[email protected]] injecting env (1) from .env.production
Hello production
> ^^

More examples

multiple `.env` files
$ echo "HELLO=local" > .env.local

$ echo "HELLO=World" > .env

$ dotenvx run -f .env.local,.env -- node index.js
[[email protected]] injecting env (1) from .env.local,.env
Hello local

Comma-separate multiple files after a single -f. Subsequent files do NOT override pre-existing variables defined in previous files or env. This follows historic principle. For example, above local wins – from the first file.

`--overload` flag
$ echo "HELLO=local" > .env.local

$ echo "HELLO=World" > .env

$ dotenvx run -f .env.local,.env --overload -- node index.js
[[email protected]] injecting env (1) from .env.local,.env
Hello World

Note that with --overload subsequent files DO override pre-existing variables defined in previous files.

`--verbose` flag
$ echo "HELLO=production" > .env.production

$ dotenvx run -f .env.production --verbose -- node index.js
[dotenvx][verbose] injecting env from /path/to/.env.production
[dotenvx][verbose] HELLO set
[[email protected]] injecting env (1) from .env.production
Hello production
`--debug` flag
$ echo "HELLO=production" > .env.production

$ dotenvx run -f .env.production --debug -- node index.js
[dotenvx][debug] configuring options
[dotenvx][debug] {"envFile":[".env.production"]}
[dotenvx][verbose] injecting env from /path/to/.env.production
[dotenvx][debug] reading env from /path/to/.env.production
[dotenvx][debug] parsing env from /path/to/.env.production
[dotenvx][debug] {"HELLO":"production"}
[dotenvx][debug] writing env from /path/to/.env.production
[dotenvx][verbose] HELLO set
[dotenvx][debug] HELLO set to production
[[email protected]] injecting env (1) from .env.production
Hello production
`--quiet` flag

Use --quiet to suppress all output (except errors).

$ echo "HELLO=production" > .env.production

$ dotenvx run -f .env.production --quiet -- node index.js
Hello production

You can also set DOTENV_QUIET=true.

$ DOTENV_QUIET=true dotenvx run -f .env.production -- node index.js
Hello production
`--log-level` flag

Set --log-level to whatever you wish. For example, to suppress warnings (risky), set log level to error:

$ echo "HELLO=production" > .env.production

$ dotenvx run -f .env.production --log-level=error -- node index.js
Hello production

Available log levels are error, warn, info, verbose, debug, silly

`--convention` flag

Load envs using Next.js' convention or dotenv-flow convention. Set --convention to nextjs or flow:

$ echo "HELLO=development local" > .env.development.local
$ echo "HELLO=local" > .env.local
$ echo "HELLO=development" > .env.development
$ echo "HELLO=env" > .env

$ dotenvx run --convention=nextjs -- node index.js
Hello development local

$ dotenvx run --convention=flow -- node index.js
Hello development local

(more conventions available upon request)

 

Encryption

Add encryption to your .env files with a single command. Use dotenvx encrypt.

$ dotenvx encrypt
◈ encrypted (.env)

encrypted .env

A DOTENV_PUBLIC_KEY (encryption key) and a DOTENV_PRIVATE_KEY (decryption key) are generated using the same public-key cryptography as Bitcoin.

More examples

`.env`
$ echo "HELLO=World" > .env
$ dotenvx encrypt
$ echo "console.log('Hello ' + process.env.HELLO)" > index.js

$ dotenvx run -- node index.js
[[email protected]] injecting env (2) from .env
Hello World
`.env.production`
$ echo "HELLO=Production" > .env.production
$ dotenvx encrypt -f .env.production
$ echo "console.log('Hello ' + process.env.HELLO)" > index.js

$ DOTENV_PRIVATE_KEY_PRODUCTION="<.env.production private key>" dotenvx run -- node index.js
[[email protected]] injecting env (2) from .env.production
Hello Production

Note the DOTENV_PRIVATE_KEY_PRODUCTION ends with _PRODUCTION. This instructs dotenvx run to load the .env.production file.

`.env.ci`
$ echo "HELLO=Ci" > .env.ci
$ dotenvx encrypt -f .env.ci
$ echo "console.log('Hello ' + process.env.HELLO)" > index.js

$ DOTENV_PRIVATE_KEY_CI="<.env.ci private key>" dotenvx run -- node index.js
[[email protected]] injecting env (2) from .env.ci
Hello Ci

Note the DOTENV_PRIVATE_KEY_CI ends with _CI. This instructs dotenvx run to load the .env.ci file. See the pattern?

combine multiple encrypted .env files
$ dotenvx set HELLO World -f .env
$ dotenvx set HELLO Production -f .env.production
$ echo "console.log('Hello ' + process.env.HELLO)" > index.js

$ DOTENV_PRIVATE_KEY="<.env private key>" DOTENV_PRIVATE_KEY_PRODUCTION="<.env.production private key>" dotenvx run -- node index.js
[[email protected]] injecting env (3) from .env, .env.production
Hello World

Note the DOTENV_PRIVATE_KEY instructs dotenvx run to load the .env file and the DOTENV_PRIVATE_KEY_PRODUCTION instructs it to load the .env.production file. See the pattern?

use directories with monorepos

Point -f at a directory to load the .env inside it. From a workspace, this makes a shared root .env available without repeating its filename.

my-monorepo/
  .env
  apps/
    web/
      index.js
$ dotenvx encrypt
$ cd apps/web

$ dotenvx get HELLO -f ../..
World

$ dotenvx run -f ../.. -- node index.js
[[email protected]] injecting env (1) from ../../.env
Hello World

With the private key in your OS secret store, dotenvx finds it automatically from any workspace on the same machine.

The directory also becomes the base when using a convention:

$ dotenvx run -f ../.. --convention=nextjs -- node index.js
[[email protected]] injecting env (1) from ../../.env.development.local, ../../.env.local, ../../.env.development, ../../.env
Hello development local

For a workspace with its own encrypted .env, run from that workspace:

$ dotenvx run -- node index.js
`--stdout`
$ echo "HELLO=World" > .env
$ dotenvx encrypt --stdout
$ dotenvx encrypt --stdout > .env.encrypted
other curves

secp256k1 is a well-known and battle tested curve, in use with Bitcoin and other cryptocurrencies, but we are open to adding support for more curves.

If your organization's compliance department requires NIST approved curves or other curves like curve25519, please reach out at [email protected].

 

Advanced

Become a dotenvx power user.

CLI 📟

Advanced CLI commands.

`run` - Variable Expansion

Reference and expand variables already on your machine for use in your .env file.

# .env
USERNAME="username"
DATABASE_URL="postgres://${USERNAME}@localhost/my_database"
// index.js
console.log('DATABASE_URL', process.env.DATABASE_URL)
$ dotenvx run --debug -- node index.js
[[email protected]] injecting env (2) from .env
DATABASE_URL postgres://username@localhost/my_database
`run` - Default Values

Use default values when environment variables are unset or empty.

# .env
# Default value syntax: use value if set, otherwise use default
DATABASE_HOST=${DB_HOST:-localhost}
DATABASE_PORT=${DB_PORT:-5432}

# Alternative syntax (no colon): use value if set, otherwise use default
API_URL=${API_BASE_URL-https://api.example.com}
// index.js
console.log('DATABASE_HOST', process.env.DATABASE_HOST)
console.log('DATABASE_PORT', process.env.DATABASE_PORT)
console.log('API_URL', process.env.API_URL)
$ dotenvx run --debug -- node index.js
[[email protected]] injecting env (3) from .env
DATABASE_HOST localhost
DATABASE_PORT 5432
API_URL https://api.example.com
`run` - Alternate Values

Use alternate values when environment variables are set and non-empty.

# .env
NODE_ENV=production

# Alternate value syntax: use alternate if set and non-empty, otherwise empty
DEBUG_MODE=${NODE_ENV:+false}
LOG_LEVEL=${NODE_ENV:+error}

# Alternative syntax (no colon): use alternate if set, otherwise empty  
CACHE_ENABLED=${NODE_ENV+true}
// index.js
console.log('NODE_ENV', process.env.NODE_ENV)
console.log('DEBUG_MODE', process.env.DEBUG_MODE)
console.log('LOG_LEVEL', process.env.LOG_LEVEL)
console.log('CACHE_ENABLED', process.env.CACHE_ENABLED)
$ dotenvx run --debug -- node index.js
[[email protected]] injecting env (4) from .env
NODE_ENV production
DEBUG_MODE false
LOG_LEVEL error
CACHE_ENABLED true
`run` - Interpolation Syntax Summary (Variable Expansion, Default/Alternate Values)

Complete reference for variable interpolation patterns supported by dotenvx:

# .env
DEFINED_VAR=hello
EMPTY_VAR=
# UNDEFINED_VAR is not set

# Default value syntax - use variable if set/non-empty, otherwise use default
TEST1=${DEFINED_VAR:-fallback}     # Result: "hello"
TEST2=${EMPTY_VAR:-fallback}       # Result: "fallback"  
TEST3=${UNDEFINED_VAR:-fallback}   # Result: "fallback"

# Default value syntax (no colon) - use variable if set, otherwise use default
TEST4=${DEFINED_VAR-fallback}      # Result: "hello"
TEST5=${EMPTY_VAR-fallback}        # Result: "" (empty, but set)
TEST6=${UNDEFINED_VAR-fallback}    # Result: "fallback"

# Alternate value syntax - use alternate if variable is set/non-empty, otherwise empty
TEST7=${DEFINED_VAR:+alternate}    # Result: "alternate"
TEST8=${EMPTY_VAR:+alternate}      # Result: "" (empty)
TEST9=${UNDEFINED_VAR:+alternate}  # Result: "" (empty)

# Alternate value syntax (no colon) - use alternate if variable is set, otherwise empty  
TEST10=${DEFINED_VAR+alternate}    # Result: "alternate"
TEST11=${EMPTY_VAR+alternate}      # Result: "alternate" (empty but set)
TEST12=${UNDEFINED_VAR+alternate}  # Result: "" (empty)

Key differences:

  • :- vs -: The colon makes empty values trigger the fallback
  • :+ vs +: The colon makes empty values not trigger the alternate
  • Default syntax (-): Use variable value or fallback
  • Alternate syntax (+): Use alternate value or empty string
`run` - Command Substitution

Add the output of a command to one of your variables in your .env file.

# .env
DATABASE_URL="postgres://$(whoami)@localhost/my_database"
// index.js
console.log('DATABASE_URL', process.env.DATABASE_URL)
$ dotenvx run --debug -- node index.js
[[email protected]] injecting env (1) from .env
DATABASE_URL postgres://yourusername@localhost/my_database
`run` - Shell Expansion

Prevent your shell from expanding inline $VARIABLES before dotenvx has a chance to inject it. Use a subshell.

$ dotenvx run --env="HELLO=World" -- sh -c 'echo Hello $HELLO'
Hello World
`run` - Multiline

Dotenvx supports multiline values. This is particularly useful in conjunction with Docker - which does not support multiline values.

# .env
MULTILINE_PEM="-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnNl1tL3QjKp3DZWM0T3u
LgGJQwu9WqyzHKZ6WIA5T+7zPjO1L8l3S8k8YzBrfH4mqWOD1GBI8Yjq2L1ac3Y/
bTdfHN8CmQr2iDJC0C6zY8YV93oZB3x0zC/LPbRYpF8f6OqX1lZj5vo2zJZy4fI/
kKcI5jHYc8VJq+KCuRZrvn+3V+KuL9tF9v8ZgjF2PZbU+LsCy5Yqg1M8f5Jp5f6V
u4QuUoobAgMBAAE=
-----END PUBLIC KEY-----"
// index.js
console.log('MULTILINE_PEM', process.env.MULTILINE_PEM)
$ dotenvx run -- node index.js
MULTILINE_PEM -----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnNl1tL3QjKp3DZWM0T3u
LgGJQwu9WqyzHKZ6WIA5T+7zPjO1L8l3S8k8YzBrfH4mqWOD1GBI8Yjq2L1ac3Y/
bTdfHN8CmQr2iDJC0C6zY8YV93oZB3x0zC/LPbRYpF8f6OqX1lZj5vo2zJZy4fI/
kKcI5jHYc8VJq+KCuRZrvn+3V+KuL9tF9v8ZgjF2PZbU+LsCy5Yqg1M8f5Jp5f6V
u4QuUoobAgMBAAE=
-----END PUBLIC KEY-----
`run` - Contextual Help

Unlike other dotenv libraries, dotenvx attempts to unblock you with contextual help.

For example, when missing a custom .env file:

$ dotenvx run -f .env.missing -- echo $HELLO
[MISSING_ENV_FILE] missing file (/Users/scottmotte/Code/dotenvx/playground/apr-16/.env.missing). fix: [echo "HELLO=World" > .env.missing]

or when missing a KEY:

$ echo "HELLO=World" > .env
$ dotenvx get GOODBYE
[MISSING_KEY] missing key (GOODBYE)
`run` - 1Password

Resolve 1Password op:// directly from your .env file.

# .env
API_KEY=op://Personal/my_api_key/password

Install the 1Password CLI and authenticate with op. Dotenvx automatically reads op:// values through op read before injecting them.

$ dotenvx run -- node index.js

Use --no-1password to leave op:// values unresolved.

$ dotenvx run --no-1password -- node index.js

The same flag is available for dotenvx get and dotenvx check.

`run` - Bitwarden

Resolve bw:// references directly from your .env file through the Bitwarden Password Manager CLI.

# .env
API_KEY="bw://My GitHub Account/password"

The reference format is bw://<item>/<field>. The item can be a human-readable Bitwarden search term or an exact item UUID. Quote the dotenv value when the item name contains spaces.

Supported fields are:

  • username
  • password
  • uri

When the vault is locked in an interactive terminal, dotenvx prompts for your Bitwarden master password.

Human-readable names are convenient, but must identify a single item. If multiple items match, Bitwarden returns an error. Use the item UUID when you need an unambiguous reference.

If Bitwarden cannot resolve a reference, dotenvx reports the error and leaves the original bw:// value unresolved.

Use --no-bitwarden to skip Bitwarden resolution intentionally.

$ dotenvx run --no-bitwarden -- node index.js

The same flag is available for dotenvx get and dotenvx check.

`run -f `

Run a command using the .env file in a directory. This is useful with monorepos.

$ dotenvx run -f ../.. -- node index.js
[[email protected]] injecting env (1) from ../../.env
Hello World
`run -f` - multiple files

Compose multiple .env files for environment variables loading, as you need.

$ echo "HELLO=local" > .env.local
$ echo "HELLO=World" > .env
$ echo "console.log('Hello ' + process.env.HELLO)" > index.js

$ dotenvx run -f .env.local,.env -- node index.js
[[email protected]] injecting env (1) from .env.local, .env
Hello local

Comma-separate multiple files after a single -f. Subsequent files do NOT override pre-existing variables defined in previous files or env. This follows historic principle. For example, above local wins – from the first file.

`run --env HELLO=String`

Set environment variables as a simple KEY=value string pair.

$ echo "HELLO=World" > .env
$ echo "console.log('Hello ' + process.env.HELLO)" > index.js

$ dotenvx run --env HELLO=String -f .env -- node index.js
[[email protected]] injecting env (1) from .env, and --env flag
Hello String
`run with Envfile redaction`

Run any command with real environment variables while automatically redacting values selected by your Envfile from stdout and stderr.

$ echo "SECRET=super-secret-value" > .env
$ echo "PUBLIC_VALUE=visible-value" >> .env
$ echo "console.log(process.env.SECRET, process.env.PUBLIC_VALUE)" > index.js

$ dotenvx spec
$ dotenvx run --quiet -- node index.js
[REDACTED] visible-value

With an Envfile, all values are redacted by default. Use redacted: false on individual declarations to leave those values visible. spec generates these settings, including public-name exceptions. If an existing environment variable takes precedence, its effective value follows the same rules. Matching is exact, so transformed or derived values are not redacted.

Without an Envfile, dotenvx run --redact -- yourcommand redacts loaded values except recognized public names (containing PUBLIC or starting with VITE, case-sensitive). _PLAIN values are redacted too. Without either an Envfile or --redact, output is not redacted. An Envfile always takes precedence: its rules apply automatically, even when --redact is passed.

When stdin, stdout, and stderr are attached to a terminal, dotenvx preserves interactive behavior on macOS and Linux systems with script available. Piped and redirected commands continue to use normal stdin, stdout, and stderr streams.

`run -- claude -p`

Run Claude in print mode with real environment variables while redacting any values it prints.

$ echo "SECRET=super-secret-value" > .env

$ dotenvx spec
$ dotenvx run --quiet -- claude -p 'Print the value of $SECRET'
[REDACTED]
`run -- claude`

Start a fully interactive Claude session. Claude receives the real values, but any values it prints are redacted.

$ echo "SECRET=super-secret-value" > .env

$ dotenvx spec
$ dotenvx run --quiet -- claude
`run -- codex exec`

Run Codex non-interactively with real environment variables while redacting any values it prints.

$ echo "SECRET=super-secret-value" > .env

$ dotenvx spec
$ dotenvx run --quiet -- codex exec 'Print the value of $SECRET'
[REDACTED]
`run -- codex`

Start a fully interactive Codex session. Codex receives the real values, but any values it prints are redacted.

$ echo "SECRET=super-secret-value" > .env

$ dotenvx spec
$ dotenvx run --quiet -- codex
`run --overload`

Override existing env variables. These can be variables already on your machine or variables loaded as files consecutively. The last variable seen will 'win'.

$ echo "HELLO=local" > .env.local
$ echo "HELLO=World" > .env
$ echo "console.log('Hello ' + process.env.HELLO)" > index.js

$ dotenvx run -f .env.local,.env --overload -- node index.js
[[email protected]] injecting env (1) from .env.local, .env
Hello World

Note that with --overload subsequent files DO override pre-existing variables defined in previous files.

`run` - Environment Variable Precedence (Container/Cloud Deployments)

When deploying applications in containers or cloud environments, you often need to override specific environment variables at runtime without modifying committed .env files. By default, dotenvx follows the historic dotenv principle: environment variables already present take precedence over .env files.

# .env.prod contains: MODEL_REGISTRY=registry.company.com/models/v1
$ echo "MODEL_REGISTRY=registry.company.com/models/v1" > .env.prod
$ echo "console.log('MODEL_REGISTRY:', process.env.MODEL_REGISTRY)" > app.js

# Without environment variable set - uses .env.prod value
$ dotenvx run -f .env.prod -- node app.js
MODEL_REGISTRY: registry.company.com/models/v1

# With environment variable set (e.g., via Azure Container Service) - environment variable takes precedence
$ MODEL_REGISTRY=registry.azure.com/models/v2 dotenvx run -f .env.prod -- node app.js
MODEL_REGISTRY: registry.azure.com/models/v2

# To force .env.prod to override environment variables, use --overload
$ MODEL_REGISTRY=registry.azure.com/models/v2 dotenvx run -f .env.prod --overload -- node app.js
MODEL_REGISTRY: registry.company.com/models/v1

For container deployments: Set environment variables through your cloud provider's UI/configuration (Azure Container Service, AWS ECS, etc.) to override specific values from committed .env files without rebuilding your application.

`DOTENV_PRIVATE_KEY=key run`

Decrypt your encrypted .env by setting DOTENV_PRIVATE_KEY before dotenvx run.

$ touch .env
$ dotenvx set HELLO encrypted
$ echo "console.log('Hello ' + process.env.HELLO)" > index.js

# check your .env.keys files for your privateKey
$ DOTENV_PRIVATE_KEY="122...0b8" dotenvx run -- node index.js
[[email protected]] injecting env (2) from .env
Hello encrypted
`DOTENV_PRIVATE_KEY_PRODUCTION=key run`

Decrypt your encrypted .env.production by setting DOTENV_PRIVATE_KEY_PRODUCTION before dotenvx run. Alternatively, this can be already set on your server or cloud provider.

$ touch .env.production
$ dotenvx set HELLO "production encrypted" -f .env.production
$ echo "console.log('Hello ' + process.env.HELLO)" > index.js

# check .env.keys for your privateKey
$ DOTENV_PRIVATE_KEY_PRODUCTION="122...0b8" dotenvx run -- node index.js
[[email protected]] injecting env (2) from .env.production
Hello production encrypted

Note the DOTENV_PRIVATE_KEY_PRODUCTION ends with _PRODUCTION. This instructs dotenvx run to load the .env.production file.

`DOTENV_PRIVATE_KEY_CI=key dotenvx run`

Decrypt your encrypted .env.ci by setting DOTENV_PRIVATE_KEY_CI before dotenvx run. Alternatively, this can be already set on your server or cloud provider.

$ touch .env.ci
$ dotenvx set HELLO "ci encrypted" -f .env.ci
$ echo "console.log('Hello ' + process.env.HELLO)" > index.js

# check .env.keys for your privateKey
$ DOTENV_PRIVATE_KEY_CI="122...0b8" dotenvx run -- node index.js
[[email protected]] injecting env (2) from .env.ci
Hello ci encrypted

Note the DOTENV_PRIVATE_KEY_CI ends with _CI. This instructs dotenvx run to load the .env.ci file. See the pattern?

`DOTENV_PRIVATE_KEY=key DOTENV_PRIVATE_KEY_PRODUCTION=key run` - Combine Multiple

Decrypt your encrypted .env and .env.production files by setting DOTENV_PRIVATE_KEY and DOTENV_PRIVATE_KEY_PRODUCTION before dotenvx run.

$ touch .env
$ touch .env.production
$ dotenvx set HELLO encrypted
$ dotenvx set HELLO "production encrypted" -f .env.production
$ echo "console.log('Hello ' + process.env.HELLO)" > index.js

# check .env.keys for your privateKeys
$ DOTENV_PRIVATE_KEY="122...0b8" DOTENV_PRIVATE_KEY_PRODUCTION="122...0b8" dotenvx run -- node index.js
[[email protected]] injecting env (3) from .env, .env.production
Hello encrypted

$ DOTENV_PRIVATE_KEY_PRODUCTION="122...0b8" DOTENV_PRIVATE_KEY="122...0b8" dotenvx run -- node index.js
[[email protected]] injecting env (3) from .env.production, .env
Hello production encrypted

Compose any encrypted files you want this way. As long as a DOTENV_PRIVATE_KEY_${environment} is set, the values from .env.${environment} will be decrypted at runtime.

`run --verbose`

Set log level to verbose. (log levels)

$ echo "HELLO=production" > .env.production
$ echo "console.log('Hello ' + process.env.HELLO)" > index.js

$ dotenvx run -f .env.production --verbose -- node index.js
loading env from .env.production (/path/to/.env.production)
HELLO set
[[email protected]] injecting env (1) from .env.production
Hello production
`run --debug`

Set log level to debug. (log levels)

$ echo "HELLO=production" > .env.production
$ echo "console.log('Hello ' + process.env.HELLO)" > index.js

$ dotenvx run -f .env.production --debug -- node index.js
process command [node index.js]
options: {"env":[],"envFile":[".env.production"]}
loading env from .env.production (/path/to/.env.production)
{"HELLO":"production"}
HELLO set
HELLO set to production
[[email protected]] injecting env (1) from .env.production
executing process command [node index.js]
expanding process command to [/opt/homebrew/bin/node index.js]
Hello production
`run --quiet`

Use --quiet to suppress all output (except errors). (log levels)

$ echo "HELLO=production" > .env.production
$ echo "console.log('Hello ' + process.env.HELLO)" > index.js

$ dotenvx run -f .env.production --quiet -- node index.js
Hello production

You can also set DOTENV_QUIET=true.

$ DOTENV_QUIET=true dotenvx run -f .env.production -- node index.js
Hello production
`run --log-level`

Set --log-level to whatever you wish. For example, to suppress warnings (risky), set log level to error:

$ echo "HELLO=production" > .env.production
$ echo "console.log('Hello ' + process.env.HELLO)" > index.js

$ dotenvx run -f .env.production --log-level=error -- node index.js
Hello production

Available log levels are error, warn, info, verbose, debug, silly (source)

`run with an Envfile`

Envfile is like a Dockerfile for your secrets.

When an Envfile is present, run and check validate the same requirements: required values, types, enums, and ranges. No validation flag is needed. run performs validation before starting your command.

# Envfile
env "DATABASE_URL", type: "url"
env "API_KEY"
env "SENTRY_DSN", optional: true
$ dotenvx run -- node index.js
[INVALID_ENV] DATABASE_URL is required; API_KEY is required

Envfile validation failures warn by default; run --strict stops before launching the command. A strict true setting in Envfile, at the root or in an active file block, also makes its validation failures fatal. check always exits with failure when validation fails. Strictness changes enforcement, not which rules are checked. Invalid Envfile syntax always stops execution.

Envfile values default to redacted: true: run masks their values in child stdout/stderr and resolved-value debug output, while the child still receives the real values. Set redacted: false for values that may appear in output. This is independent of encrypted: true, which controls encryption in encrypt, set, and protect.

The synchronous dotenvx.config() API also reads Envfile from the current working directory and validates the final resolved values using the same rules, including selected file blocks. Validation failures warn and are returned in { parsed, error }; config({ strict: true }) or an applicable Envfile strict true setting makes failures throw. ignore: ['INVALID_ENV'] suppresses validation failures. Invalid Envfile syntax always throws before loading values. With an Envfile, config() automatically redacts subsequent writes through Node's stdout/stderr streams, including existing console.log and console.error calls. Environment values and returned values stay unchanged. Repeated calls retain earlier secrets and add newly loaded ones. An unfinished secret prefix is masked at shutdown. Direct native writes and child processes inheriting file descriptors bypass these wrappers; use dotenvx run to capture child output.

file ".env.development" do
  env "BASE_URL", type: "url", redacted: false
  env "TOKEN_SECRET", encrypted: true
end

spec generates declarations with encryption and redaction required by default. Encryption is required by default: root and file-level encrypted settings are not supported. Use encrypted: false on individual declarations to permit plaintext. Entirely plaintext inputs generate no encryption exceptions except for keys ending in _PLAIN, so the next encrypt encrypts the remaining values. _PLAIN keys get encrypted: false only when none of their assignments contain ciphertext; code-only references do not infer this exception. For files that already contain ciphertext, spec preserves plaintext values with per-key exceptions. Source files are never modified.

spec infers redacted: false for names starting with PUBLIC, VITE, NEXT_PUBLIC, or NUXT_PUBLIC, or containing PUBLIC anywhere (case-sensitive). This affects visibility only; public names still require encryption unless explicitly exempted. Other names, including those ending in _PLAIN, remain redacted.

dotenvx spec always generates Envfile with strict true at the top, including with --overwrite or --stdout. Generated policies stop commands on validation failures. Existing policies that omit strict continue to warn.

If an Envfile already exists, spec leaves it untouched and prints ○ Envfile already exists [edit or run: spec --overwrite]. Use spec --overwrite to regenerate it from the selected inputs, replacing custom rules, comments, and blocks for files absent from this machine. Interactive overwrite runs show the full file-and-code checklist with the prompt Recreate Envfile from .env files and code. Noninteractive runs use .env.example and .env, or the file selected with -f. Input validation finishes before the old Envfile is replaced; cancelled selection leaves it untouched. Symlinked Envfiles cannot be overwritten.

Use dotenvx spec --stdout to print the generated Envfile without writing it, or dotenvx spec --stdout -f .env.production to select an input file. This works even when an Envfile already exists; --stdout leaves it untouched even with --overwrite. Prompts and progress stay on stderr so stdout contains only the generated content.

Root and file-level redacted settings are not supported. Use per-key redacted: false to permit output visibility. When selected files specify conflicting options, the first file's explicit option wins. check previews values with policy-driven redaction and reports issues per key.

`run with Envfile file rules`

Use exact file blocks to override rules for selected files:

env "HELLO"
env "PORT", type: "port"
env "STRIPE_SECRET_KEY", optional: true

file ".env.production" do
  env "STRIPE_SECRET_KEY", required: true
  env "PORT", min: 1024
end

Profiles select file combinations:

profile "development", files: [".env.local", ".env"]
profile "production", files: [".env.production"]

Selection order is explicit -f, --profile, DOTENV_FILE / DOTENV_PATH / DOTENV_F, then DOTENV_ENV, APP_ENV, RAILS_ENV, RACK_ENV, MIX_ENV, and NODE_ENV. Without a selector, the development profile is used if declared; otherwise .env. Explicit unknown profiles fail.

Both dotenvx run -f .env.production -- node index.js and dotenvx check -f .env.production use the production rules. Block declarations inherit top-level options and override only the options they specify. Encryption and redaction are specified per key.

Paths in file blocks are relative to the Envfile. They match the selected paths exactly after path normalization (./.env.production matches .env.production); they are not basename matches or globs. Directory inputs and DOTENV_FILE use their resolved file paths.

When no file block matches, top-level rules apply. Selected file blocks override explicit options in file order: the first file wins. Policy selection is independent of where the final value comes from, including shell values and --overload. A selected missing file still activates its block. Blocks cannot be nested, and duplicate declarations within one scope or duplicate file blocks are errors.

`check`

Validate the final resolved environment against your Envfile, using the same loader and precedence as run:

$ dotenvx check -f .env.local -f .env
✔ PORT=3000 # .env.local:1

$ dotenvx check -f .env.production
☠ PORT=bad # .env.production:4 !port

Envfile file blocks define policy; they do not select files to load. By default, check uses the same profile and file selection as run. Use repeated -f flags or --convention to select layers. DOTENV_FILE and its aliases are also supported.

…view the full README on GitHub.

// faq

What is dotenvx?

a secure dotenv—from the creator of `dotenv`. It is open-source on GitHub.

Is dotenvx free to use?

dotenvx is open-source under the BSD-3-Clause license, so it is free to use.

What category does dotenvx belong to?

dotenvx is listed under uncategorized in the Claudeers registry of Claude-compatible tools.

1 views
★ 5,828 stars
unclaimed
updated 1 day ago

// embed badge

dotenvx on Claudeers
[![Claudeers](https://claudeers.com/api/badge/dotenvx.svg)](https://claudeers.com/dotenvx)

// retro hit counter

dotenvx hit counter
[![Hits](https://claudeers.com/api/counter/dotenvx.svg)](https://claudeers.com/dotenvx)

// reviews

// guestbook

0/500

// related in Uncategorized / Others

🔓

Fair-code workflow automation platform with native AI capabilities. Combine visual building with custom code, self-host or cloud, 400+ integrations.

// uncategorizedn8n-io/⟨TypeScript⟩★ 206,731◷ NOASSERTION[ claude ]
🔓

The agent engineering platform.

// uncategorizedlangchain-ai/⟨Python⟩★ 147,503◷ MIT[ claude ]
🔓

FULL Augment Code, Claude Code, Cluely, CodeBuddy, Comet, Cursor, Devin AI, Junie, Kiro, Leap.new, Lovable, Manus, NotionAI, Orchids.app, Perplexity, Poke, Q…

// uncategorizedx1xhlol/★ 143,887◷ GPL-3.0[ claude ]
🔓

100+ AI Agent & RAG apps you can actually run — clone, customize, ship.

// uncategorizedShubhamsaboo/⟨Python⟩★ 140,637◷ Apache-2.0[ claude ]

// built by

1 of its contributors also build on official projects — java-sdk, php-sdk, python-sdk, ruby-sdk

→ see how dotenvx connects across the ecosystem