Secrets
Alchemy provides built-in secret management to securely handle sensitive values like API keys, passwords, and credentials. Secrets are automatically encrypted when stored in state files, protecting your sensitive data.
What are Secrets?
Secrets in Alchemy are wrapper objects that:
Encrypt values when persisted to state files
Prevent accidental logging by overriding toString()
Maintain type safety through TypeScript
Integrate seamlessly with resource props
Without using secrets, sensitive values are stored in plain text in state files. Always wrap sensitive data with alchemy.secret().
Creating Secrets
From Environment Variables
The recommended approach uses alchemy.secret.env:
import { alchemy } from "alchemy" ;
const app = await alchemy ( "my-app" , {
password: process . env . ALCHEMY_PASSWORD
});
// Best practice: Using alchemy.secret.env
const apiKey = alchemy . secret . env . API_KEY ;
// Also works: Direct wrapping
const dbPassword = alchemy . secret ( process . env . DB_PASSWORD );
alchemy.secret.env.VAR_NAME automatically throws an error if the environment variable is not set, providing better error messages than manual checking.
From Literal Values
You can also create secrets from literal values:
const token = alchemy . secret ( "my-secret-token" );
const password = alchemy . secret ( "super-secure-password" );
Avoid hardcoding secrets in source code. Always use environment variables for production deployments.
Encryption Password
Secrets require a password for encryption. Set it globally:
const app = await alchemy ( "my-app" , {
password: process . env . ALCHEMY_PASSWORD
});
Or via environment variables:
export ALCHEMY_PASSWORD = "your-encryption-password"
bun ./alchemy.run.ts
The password is used to encrypt/decrypt secrets in state files. Without it, operations involving secrets will fail.
Using Secrets in Resources
Secrets integrate seamlessly with resource properties:
import { Worker } from "alchemy/cloudflare" ;
import { alchemy } from "alchemy" ;
const app = await alchemy ( "my-app" , {
password: process . env . ALCHEMY_PASSWORD
});
const worker = await Worker ( "api" , {
entrypoint: "./src/worker.ts" ,
bindings: {
API_KEY: alchemy . secret . env . API_KEY ,
DATABASE_URL: alchemy . secret . env . DATABASE_URL
}
});
await app . finalize ();
How Secrets Work
Encryption in State Files
When resources are saved to state:
// .alchemy/my-app/dev/worker.json
{
"props" : {
"bindings" : {
"API_KEY" : {
"@secret" : "encrypted-base64-string-here..."
}
}
}
}
The actual value is encrypted using AES-256-GCM with your password.
Runtime Access
Secrets expose their values through the unencrypted property:
const apiKey = alchemy . secret . env . API_KEY ;
// Access the actual value
console . log ( apiKey . unencrypted ); // "actual-api-key-value"
// The secret itself is safe to log
console . log ( apiKey ); // "Secret(API_KEY)"
console . log ( apiKey . toString ()); // "Secret(API_KEY)"
Secrets automatically prevent accidental exposure by overriding toString() and util.inspect.custom.
Secret Utilities
Wrapping Values
Ensure a value is a secret:
import { Secret } from "alchemy" ;
function processApiKey ( key : string | Secret < string >) {
// Wrap if needed
const secret = Secret . wrap ( key );
// Now guaranteed to be a Secret
}
Unwrapping Values
Extract the raw value:
import { Secret } from "alchemy" ;
function callApi ( key : string | Secret < string >) {
// Unwrap to get the actual value
const actualKey = Secret . unwrap ( key );
// Use in API call
return fetch ( "https://api.example.com" , {
headers: { "Authorization" : `Bearer ${ actualKey } ` }
});
}
Type Guards
Check if a value is a secret:
import { isSecret } from "alchemy" ;
function handleValue ( value : unknown ) {
if ( isSecret ( value )) {
console . log ( "This is a secret:" , value . name );
console . log ( "Encrypted value:" , value . unencrypted );
}
}
Implementing Resources with Secrets
When creating custom resources that accept secrets:
import { Resource , Secret , type Context } from "alchemy" ;
export interface MyResourceProps {
/**
* API key for authentication
* Use alchemy.secret() to securely store this value
*/
apiKey : string | Secret < string >;
}
export interface MyResource {
id : string ;
/**
* API key (always wrapped as Secret in output)
*/
apiKey : Secret < string >;
}
export const MyResource = Resource (
"provider::MyResource" ,
async function (
this : Context < MyResource >,
id : string ,
props : MyResourceProps
) : Promise < MyResource > {
// Unwrap for API calls
const actualKey = Secret . unwrap ( props . apiKey );
await fetch ( "https://api.provider.com/resources" , {
headers: {
"Authorization" : `Bearer ${ actualKey } `
},
method: "POST" ,
body: JSON . stringify ({ name: id })
});
// Wrap for output (ensures it's always a Secret)
return {
id ,
apiKey: Secret . wrap ( props . apiKey )
};
}
);
Always unwrap secrets when making API calls, and wrap them in the resource output.
Scoped Passwords
Different scopes can use different encryption passwords:
import { alchemy } from "alchemy" ;
const app = await alchemy ( "my-app" , {
password: process . env . ALCHEMY_PASSWORD
});
// Standard secrets use app password
const apiKey = alchemy . secret . env . API_KEY ;
await alchemy . run ( "secure-service" , {
password: process . env . SECURE_SERVICE_PASSWORD
}, async ( scope ) => {
// Secrets in this scope use different password
const serviceKey = alchemy . secret . env . SERVICE_KEY ;
const resource = await MyResource ( "secure" , {
apiKey: serviceKey
});
});
await app . finalize ();
Scoped passwords enable different teams or services to manage their own encryption keys.
Secret Recovery
If you lose your encryption password:
Option 1: Erase Secrets
bun ./alchemy.run.ts --erase-secrets --force
This treats all secrets as undefined and allows you to redeploy with a new password.
--erase-secrets removes encrypted values permanently. You’ll need to provide new secret values after erasing.
Option 2: Manually Update State
Edit state files in .alchemy/ to remove or update encrypted values.
Option 3: Destroy and Recreate
bun ./alchemy.run.ts --destroy
# Update password
export ALCHEMY_PASSWORD = "new-password"
bun ./alchemy.run.ts
Environment Variable Patterns
Named Secrets
Create secrets with custom names for better debugging:
const apiKey = alchemy . secret ( process . env . API_KEY , "stripe-api-key" );
console . log ( apiKey . toString ()); // "Secret(stripe-api-key)"
Validation
Validate secrets before use:
const apiKey = alchemy . secret . env . API_KEY ;
if ( ! apiKey . unencrypted . startsWith ( "sk_" )) {
throw new Error ( "Invalid API key format" );
}
Conditional Secrets
Use different secrets based on stage:
const app = await alchemy ( "my-app" );
const apiKey = app . stage === "prod"
? alchemy . secret . env . PROD_API_KEY
: alchemy . secret . env . DEV_API_KEY ;
Type Safety
Secrets maintain full TypeScript type safety:
import { Secret } from "alchemy" ;
// Typed secret
const config : Secret <{ apiKey : string ; endpoint : string }> = alchemy . secret ({
apiKey: process . env . API_KEY ! ,
endpoint: "https://api.example.com"
});
// TypeScript knows the shape
const endpoint = config . unencrypted . endpoint ;
Security Best Practices
Never hardcode secrets
Always use environment variables or external secret managers
Rotate passwords regularly
Change encryption passwords periodically
Use strong passwords
Encryption password should be long and random (32+ characters)
Separate dev and prod passwords
Use different passwords for different stages
Don't commit .env files
Add .env to .gitignore
Use secret managers in CI/CD
Store passwords in GitHub Secrets, AWS Secrets Manager, etc.
Common Patterns
Database Credentials
const app = await alchemy ( "my-app" , {
password: process . env . ALCHEMY_PASSWORD
});
const database = await PostgresDatabase ( "db" , {
connectionString: alchemy . secret . env . DATABASE_URL
});
OAuth Tokens
const githubToken = alchemy . secret . env . GITHUB_TOKEN ;
const integration = await GitHubApp ( "bot" , {
token: githubToken ,
repository: "my-org/my-repo"
});
Multi-Provider Credentials
const app = await alchemy ( "my-app" , {
password: process . env . ALCHEMY_PASSWORD
});
const awsKey = alchemy . secret . env . AWS_ACCESS_KEY ;
const cloudflareToken = alchemy . secret . env . CLOUDFLARE_API_TOKEN ;
Debugging Secrets
Enable secret logging for debugging (use carefully):
const apiKey = alchemy . secret . env . API_KEY ;
if ( process . env . DEBUG_SECRETS === "true" ) {
console . log ( "API Key (debug):" , apiKey . unencrypted );
} else {
console . log ( "API Key:" , apiKey ); // "Secret(API_KEY)"
}
Never enable secret debugging in production or commit debug logs.
Next Steps
Resources Learn how to use secrets in resource properties
State Management Understand how secrets are stored in state
Scopes Manage scope-level password configuration
Environment Variables Best practices for environment variable management