Skip to main content

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

1
Creation
2
When you create a secret, the value is wrapped in a Secret object:
3
const apiKey = alchemy.secret(process.env.API_KEY);
// Secret { unencrypted: "sk-1234...", name: "alchemy:anonymous-secret-0" }
4
Storage
5
When the resource is saved to state, the secret is encrypted:
6
// In memory: unencrypted
const secret = alchemy.secret("my-secret-value");

// In state file: encrypted
{ "@secret": "U2FsdGVkX1..." }
7
Retrieval
8
When state is loaded, secrets are automatically decrypted using the password:
9
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

1
Use Environment Variables
2
Store sensitive values in environment variables, not in code:
3
// ✅ Good
API_KEY: alchemy.secret.env.API_KEY

// ❌ Bad - Hardcoded secret
API_KEY: alchemy.secret("sk-1234567890abcdef")
4
Set a Strong Password
5
Use a strong, random password for encryption:
6
# Generate a secure password
openssl rand -base64 32 > .alchemy-password

# Use it in your environment
export ALCHEMY_PASSWORD=$(cat .alchemy-password)
7
Never Commit Secrets
8
Add sensitive files to .gitignore:
9
.env
.env.local
.alchemy-password
*.key
*.pem
10
Use Named Secrets for Debugging
11
API_KEY: alchemy.secret(process.env.API_KEY, "stripe-api-key"),
DB_PASS: alchemy.secret(process.env.DB_PASS, "postgres-password")
12
Validate Secrets Exist
13
Check for required environment variables early:
14
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

# 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

Build docs developers (and LLMs) love