Managing Secrets
Alchemy provides a built-in Secret system for handling sensitive values like API keys, passwords, and credentials. Secrets are automatically encrypted when stored in state files using a password.
Creating Secrets
Use alchemy.secret() to wrap sensitive values:
import alchemy from "alchemy" ;
import { Worker } from "alchemy/cloudflare" ;
const app = await alchemy ( "my-app" , {
password: process . env . ALCHEMY_PASSWORD // Required for secrets
});
const worker = await Worker ( "api" , {
entrypoint: "./src/index.ts" ,
bindings: {
API_KEY: alchemy . secret ( process . env . API_KEY ),
DB_PASSWORD: alchemy . secret ( process . env . DB_PASSWORD )
}
});
await app . finalize ();
Secrets require a password to be set. Without a password, secret operations will fail.
Setting the Password
The password is used to encrypt and decrypt secrets. You can provide it in two ways:
Global Password
Set the password when creating your application:
const app = await alchemy ( "my-app" , {
password: process . env . ALCHEMY_PASSWORD
});
Environment Variable
Alchemy automatically reads from the ALCHEMY_PASSWORD environment variable:
export ALCHEMY_PASSWORD = "my-secure-password"
bun ./alchemy.run.ts
ALCHEMY_PASSWORD = my-secure-password
API_KEY = sk-1234567890abcdef
DB_PASSWORD = super-secret-password
Never commit your .env file or password to version control. Add .env to .gitignore.
Secret from Environment Variables
Alchemy provides a convenient helper for creating secrets from environment variables:
Using secret.env
const worker = await Worker ( "api" , {
entrypoint: "./src/index.ts" ,
bindings: {
// Preferred: Better error messages
API_KEY: alchemy . secret . env . API_KEY ,
DB_PASSWORD: alchemy . secret . env . DB_PASSWORD
}
});
Using secret()
const worker = await Worker ( "api" , {
entrypoint: "./src/index.ts" ,
bindings: {
// Also valid
API_KEY: alchemy . secret ( process . env . API_KEY ),
DB_PASSWORD: alchemy . secret ( process . env . DB_PASSWORD )
}
});
Use alchemy.secret.env.VAR_NAME for better error messages when environment variables are missing.
How Secrets Work
Encryption in State Files
When resources are saved to state files (.alchemy/), secrets are automatically encrypted:
{
"props" : {
"apiKey" : {
"@secret" : "encrypted-value-using-password-here..."
}
}
}
The encrypted value can only be decrypted with the correct password.
Secret Lifecycle
When you create a secret, the value is wrapped in a Secret object:
const apiKey = alchemy . secret ( process . env . API_KEY );
// Secret { unencrypted: "sk-1234...", name: "alchemy:anonymous-secret-0" }
When the resource is saved to state, the secret is encrypted:
// In memory: unencrypted
const secret = alchemy . secret ( "my-secret-value" );
// In state file: encrypted
{ "@secret" : "U2FsdGVkX1..." }
When state is loaded, secrets are automatically decrypted using the password:
const worker = await Worker ( "api" , { /* ... */ });
// Secrets are decrypted and available in worker.bindings
Named Secrets
You can assign names to secrets for better debugging:
const worker = await Worker ( "api" , {
entrypoint: "./src/index.ts" ,
bindings: {
API_KEY: alchemy . secret ( process . env . API_KEY , "openai-api-key" ),
DB_PASSWORD: alchemy . secret ( process . env . DB_PASSWORD , "postgres-password" )
}
});
Named secrets appear in logs and error messages:
Secret(openai-api-key)
Secret(postgres-password)
Secret Wrapping and Unwrapping
The Secret class provides utilities for working with secret values:
Wrap
Ensure a value is wrapped in a Secret:
import { Secret } from "alchemy" ;
const secret = Secret . wrap ( process . env . API_KEY );
// If already a Secret, returns as-is
// Otherwise, wraps in a new Secret
Unwrap
Extract the unencrypted value from a Secret:
import { Secret } from "alchemy" ;
const apiKey = alchemy . secret ( process . env . API_KEY );
const plainValue = Secret . unwrap ( apiKey );
// Returns the original unencrypted value
Be careful when unwrapping secrets. Avoid logging or exposing unencrypted values.
Type Guard
Check if a value is a Secret:
import { isSecret } from "alchemy" ;
const value = alchemy . secret ( "my-secret" );
if ( isSecret ( value )) {
console . log ( "This is a secret!" );
// TypeScript knows value is Secret<string>
}
Secret Safety Features
toString Protection
Secrets override toString() to prevent accidental exposure:
const apiKey = alchemy . secret ( process . env . API_KEY );
console . log ( apiKey );
// Output: Secret(alchemy:anonymous-secret-0)
// NOT: sk-1234567890abcdef
console.log Protection
Secrets implement custom inspect for Node.js:
const apiKey = alchemy . secret ( process . env . API_KEY );
console . log ( apiKey );
// Output: Secret(alchemy:anonymous-secret-0)
This prevents secrets from accidentally appearing in logs or debug output.
Scoped Secrets
You can use different passwords for different scopes:
import alchemy from "alchemy" ;
const app = await alchemy ( "my-app" );
await alchemy . run ( "production-resources" , {
password: process . env . PROD_PASSWORD
}, async () => {
const prodWorker = await Worker ( "prod-api" , {
entrypoint: "./src/index.ts" ,
bindings: {
// This secret is encrypted with PROD_PASSWORD
API_KEY: alchemy . secret ( process . env . PROD_API_KEY )
}
});
});
await alchemy . run ( "staging-resources" , {
password: process . env . STAGING_PASSWORD
}, async () => {
const stagingWorker = await Worker ( "staging-api" , {
entrypoint: "./src/index.ts" ,
bindings: {
// This secret is encrypted with STAGING_PASSWORD
API_KEY: alchemy . secret ( process . env . STAGING_API_KEY )
}
});
});
await app . finalize ();
Recovering from Lost Passwords
If you lose your encryption password, you can erase secrets and start fresh:
bun ./alchemy.run.ts --erase-secrets --force
This will treat all secrets as undefined. You’ll need to re-deploy with new secret values.
Best Practices
Use Environment Variables
Store sensitive values in environment variables, not in code:
// ✅ Good
API_KEY : alchemy . secret . env . API_KEY
// ❌ Bad - Hardcoded secret
API_KEY : alchemy . secret ( "sk-1234567890abcdef" )
Use a strong, random password for encryption:
# Generate a secure password
openssl rand -base64 32 > .alchemy-password
# Use it in your environment
export ALCHEMY_PASSWORD = $( cat .alchemy-password )
Add sensitive files to .gitignore:
.env
.env.local
.alchemy-password
*.key
*.pem
Use Named Secrets for Debugging
API_KEY : alchemy . secret ( process . env . API_KEY , "stripe-api-key" ),
DB_PASS : alchemy . secret ( process . env . DB_PASS , "postgres-password" )
Check for required environment variables early:
const requiredEnvVars = [ "API_KEY" , "DB_PASSWORD" , "ALCHEMY_PASSWORD" ];
for ( const varName of requiredEnvVars ) {
if ( ! process . env [ varName ]) {
throw new Error ( `Missing required environment variable: ${ varName } ` );
}
}
const app = await alchemy ( "my-app" , {
password: process . env . ALCHEMY_PASSWORD
});
Example: Complete Secret Setup
.env.example
alchemy.run.ts
# Copy this file to .env and fill in the values
ALCHEMY_PASSWORD = your-encryption-password
API_KEY = your-api-key
DB_PASSWORD = your-database-password
CLOUDFLARE_API_KEY = your-cloudflare-api-key
CLOUDFLARE_ACCOUNT_ID = your-account-id
Next Steps