Skip to main content

alchemy.secret()

Wraps a sensitive value so it will be encrypted when stored in state files. Requires a password to be set either globally in the alchemy application options or locally in an alchemy.run() scope.

Signature

function secret<T = string>(
  unencrypted: T | undefined,
  name?: string
): Secret<T>

Parameters

unencrypted
T
required
The sensitive value to encrypt in state files. Cannot be undefined.
name
string
Optional name for the secret. Used for debugging and logging. If not provided, an auto-generated name will be used.

Returns

Secret
Secret<T>
A Secret wrapper that encrypts the value when serialized to state files.
unencrypted
T
The unwrapped sensitive value. Only accessible in code, never stored in state files.
name
string
The name of the secret.
type
'secret'
Type identifier for the Secret wrapper.

Environment Variable Helper

alchemy.secret.env

A convenient helper for creating secrets from environment variables with better error messages.
alchemy.secret.env.API_KEY
// Equivalent to: alchemy.secret(alchemy.env("API_KEY"))

alchemy.secret.env("API_KEY")
// Same as above
Advantages over alchemy.secret(process.env.X):
  • Automatically throws if the environment variable is not set
  • Provides clear error messages with the variable name
  • More concise syntax

Password Configuration

Secrets require a password for encryption/decryption. The password can be provided in two ways:

Global Password

Set a password when creating the application scope:
const app = await alchemy("my-app", {
  password: process.env.SECRET_PASSPHRASE
});

const resource = await Resource("my-resource", {
  apiKey: alchemy.secret(process.env.API_KEY)
});

Scoped Password

Set a password for a specific scope using alchemy.run():
await alchemy.run("secure-scope", {
  password: process.env.SCOPE_SECRET_PASSPHRASE
}, async () => {
  const resource = await Resource("my-resource", {
    apiKey: alchemy.secret(process.env.API_KEY)
  });
});

Secret Class

The Secret class provides static methods for working with secrets:

Secret.wrap()

Ensures a value is wrapped in a Secret.
static wrap<T>(value: T | Secret<T>): Secret<T>
value
T | Secret<T>
required
The value to wrap. If already a Secret, returns it unchanged.
Example:
const wrapped = Secret.wrap("my-value");
const alreadyWrapped = Secret.wrap(wrapped); // Returns the same Secret

Secret.unwrap()

Unwraps a Secret if it is wrapped, otherwise returns the value.
static unwrap<T, U = T>(value: T | Secret<U>): T | U
value
T | Secret<U>
required
The value to unwrap.
Example:
const secret = alchemy.secret("my-password");
const unwrapped = Secret.unwrap(secret); // "my-password"
const plain = Secret.unwrap("plain-value"); // "plain-value"

Type Guard

isSecret()

Checks if a value is a Secret wrapper.
function isSecret<T = string>(value: any): value is Secret<T>
value
any
required
The value to check.
Returns: true if the value is a Secret, false otherwise. Example:
import { isSecret } from "alchemy";

const secret = alchemy.secret("password");
if (isSecret(secret)) {
  console.log(secret.name);
}

Examples

Basic Usage

import { alchemy } from "alchemy";

const app = await alchemy("my-app", {
  password: process.env.SECRET_PASSPHRASE
});

// Create a secret from an environment variable
const apiKey = alchemy.secret(process.env.API_KEY);

const worker = await Worker("api", {
  bindings: {
    API_KEY: apiKey
  },
  entrypoint: "./src/worker.ts"
});

await app.finalize();

Using Environment Variable Helper

import { alchemy } from "alchemy";

const app = await alchemy("my-app", {
  password: process.env.SECRET_PASSPHRASE
});

// Preferred: Better error messages
const apiKey = alchemy.secret.env.API_KEY;
const dbPassword = alchemy.secret.env.DATABASE_PASSWORD;

const database = await Database("db", {
  password: dbPassword
});

const worker = await Worker("api", {
  bindings: {
    API_KEY: apiKey
  },
  entrypoint: "./src/worker.ts"
});

await app.finalize();

Flexible Input Types

import { alchemy, Secret } from "alchemy";

interface MyResourceProps {
  // Accept either a plain string or a Secret
  password: string | Secret;
}

const MyResource = Resource(
  "my::Resource",
  async function (
    this: Context<MyResource>,
    id: string,
    props: MyResourceProps
  ): Promise<MyResource> {
    // Unwrap the secret for API calls
    const password = Secret.unwrap(props.password);
    
    const result = await api.create({
      password // plain string for API
    });

    // Wrap the secret for storage
    return {
      id,
      password: Secret.wrap(props.password)
    };
  }
);

// Usage: Can pass either a string or Secret
const resource1 = await MyResource("r1", {
  password: "plain-string" // Auto-wrapped
});

const resource2 = await MyResource("r2", {
  password: alchemy.secret("encrypted") // Already wrapped
});

State File Representation

When secrets are stored in state files (.alchemy/{stage}/{resource}.json), they are encrypted:
{
  "kind": "cloudflare::Worker",
  "id": "api",
  "props": {
    "bindings": {
      "API_KEY": {
        "@secret": "U2FsdGVkX1+K9j3h..." // Encrypted value
      }
    }
  },
  "output": {
    "url": "https://api.example.workers.dev"
  }
}

Scoped Secrets

import { alchemy } from "alchemy";

const app = await alchemy("my-app");

// Different secrets for different scopes
await alchemy.run("production-api", {
  password: process.env.PROD_SECRET_PASSPHRASE
}, async () => {
  const prodDb = await Database("db", {
    password: alchemy.secret(process.env.PROD_DB_PASSWORD)
  });
});

await alchemy.run("staging-api", {
  password: process.env.STAGING_SECRET_PASSPHRASE
}, async () => {
  const stagingDb = await Database("db", {
    password: alchemy.secret(process.env.STAGING_DB_PASSWORD)
  });
});

await app.finalize();

Error Recovery

If you lose the encryption password, you can use --erase-secrets to recover:
# This will treat all secrets as undefined and allow you to redeploy
bun ./alchemy.run.ts --force --erase-secrets

Security Best Practices

  1. Never commit passwords to version control: Store passwords in environment variables or use a secret management service.
  2. Use different passwords for different stages:
    const password = process.env[`${stage.toUpperCase()}_SECRET_PASSPHRASE`];
    
  3. Rotate passwords periodically: Update your password and redeploy to re-encrypt all secrets.
  4. Use alchemy.secret.env for better error messages:
    // ✅ Good: Clear error if API_KEY is missing
    const apiKey = alchemy.secret.env.API_KEY;
    
    // ❌ Less clear: Generic error message
    const apiKey = alchemy.secret(process.env.API_KEY);
    
  5. Don’t log or print secrets:
    console.log(secret); // Prints: Secret(API_KEY)
    console.log(secret.unencrypted); // ⚠️ Exposes the secret!
    

Build docs developers (and LLMs) love