Skip to main content

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

1

Never hardcode secrets

Always use environment variables or external secret managers
2

Rotate passwords regularly

Change encryption passwords periodically
3

Use strong passwords

Encryption password should be long and random (32+ characters)
4

Separate dev and prod passwords

Use different passwords for different stages
5

Don't commit .env files

Add .env to .gitignore
6

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

Build docs developers (and LLMs) love