Skip to main content

Overview

Secrets are encrypted environment variables for sensitive data like API keys, tokens, and credentials. Unlike plain environment variables, secrets:
  • Are encrypted at rest
  • Never appear in logs or error messages
  • Can only be set, not read via API
  • Are bound to your Worker at runtime

Creating Secrets

Interactive Mode

Create a secret with interactive prompt:
wrangler secret put API_KEY
You’ll be prompted to enter the secret value (input is hidden):
Enter a secret value: ********
🌀 Creating the secret for the Worker "my-worker"
 Success! Uploaded secret API_KEY

From stdin

Pipe secret values from other commands:
echo "my-secret-value" | wrangler secret put API_KEY
# From a file
cat api-key.txt | wrangler secret put API_KEY
# From password manager
pass show api-key | wrangler secret put API_KEY

Bulk Secret Upload

Upload multiple secrets at once:
wrangler secret bulk secrets.json

JSON Format

Create secrets.json:
{
  "API_KEY": "abc123",
  "DATABASE_URL": "postgres://...",
  "STRIPE_KEY": "sk_test_..."
}
Then upload:
wrangler secret bulk secrets.json

Environment File Format

Alternatively, use .env format:
API_KEY=abc123
DATABASE_URL=postgres://...
STRIPE_KEY=sk_test_...
wrangler secret bulk .env.production

From stdin

cat secrets.json | wrangler secret bulk

Bulk Upload Implementation

The bulk command validates and uploads secrets efficiently:
// From secret/index.ts:582-635
export async function parseBulkInputToObject(
  input?: string
): Promise<BulkInputResult | undefined> {
  let content: Record<string, string>;
  let secretSource: "file" | "stdin";
  let secretFormat: "json" | "dotenv";

  if (input) {
    secretSource = "file";
    const jsonFilePath = path.resolve(input);
    try {
      const fileContent = readFileSync(jsonFilePath);
      try {
        content = parseJSON(fileContent) as Record<string, string>;
        secretFormat = "json";
      } catch (e) {
        content = dotenvParse(fileContent);
        secretFormat = "dotenv";
      }
    } catch (e) {
      throw new FatalError(
        `The contents of "${input}" is not valid JSON: "${e}"`
      );
    }
    validateFileSecrets(content, input);
  }
  return { content, secretSource, secretFormat };
}

Listing Secrets

View all secrets for a Worker:
wrangler secret list

Output Formats

Pretty format (default):
Secret Name: API_KEY

Secret Name: DATABASE_URL

Secret Name: STRIPE_KEY
JSON format:
wrangler secret list --format json
[
  {
    "name": "API_KEY"
  },
  {
    "name": "DATABASE_URL"
  },
  {
    "name": "STRIPE_KEY"
  }
]
Secret values cannot be retrieved via CLI or API. You can only see secret names, not their values.

Deleting Secrets

Remove a secret:
wrangler secret delete API_KEY
You’ll be asked to confirm:
? Are you sure you want to permanently delete the secret API_KEY on the Worker my-worker? (y/N)
🌀 Deleting the secret API_KEY on the Worker my-worker
 Success! Deleted secret API_KEY

Secrets with Versions

When using Worker versions, secrets require special handling to avoid accidental deployments.

Version Secret Commands

Use the version-specific commands:
# Create/update secret (creates new version)
wrangler versions secret put API_KEY

# List secrets in deployed versions
wrangler versions secret list

# Delete secret (creates new version)
wrangler versions secret delete API_KEY

Creating Version with Secret

When you add a secret using wrangler versions secret put, it:
1

Prompts for secret value

Enter a secret value: ********
2

Creates new version

The command copies the latest version with the new secret:
// From versions/secrets/put.ts:73-98
const versions = await fetchResult(
  config,
  `/accounts/${accountId}/workers/scripts/${scriptName}/versions`
).items;

const latestVersion = versions[0];

const newVersion = await copyWorkerVersionWithNewSecrets({
  config,
  accountId,
  scriptName,
  versionId: latestVersion.id,
  secrets: [{ name: args.key, value: secretValue }],
  versionMessage: args.message ?? `Updated secret "${args.key}"`,
  versionTag: args.tag,
});
3

Returns new version ID

 Success! Created version a1b2c3d4 with secret API_KEY.
➡️  To deploy this version to production traffic use the command "wrangler versions deploy".

Version Secret List

List secrets in currently deployed versions:
wrangler versions secret list
Output:
-- Version a1b2c3d4-e5f6-7890-abcd-ef1234567890 (80%) secrets --
Secret Name: API_KEY
Secret Name: DATABASE_URL

-- Version b2c3d4e5-f6a7-8901-bcde-f12345678901 (20%) secrets --
Secret Name: API_KEY
Secret Name: OLD_DATABASE_URL
This shows:
  • Which secrets exist in each deployed version
  • Traffic percentage for each version
  • Secret name differences between versions

Latest Version Only

wrangler versions secret list --latest-version
-- Version a1b2c3d4 (0%) secrets --
Secret Name: API_KEY
Secret Name: DATABASE_URL
Secret Name: NEW_FEATURE_KEY
Shows secrets in the most recently uploaded version (even if not deployed).

Standard vs. Version Secrets

Standard Deployment Secrets

wrangler secret put API_KEY
  • ✅ Simple single command
  • ✅ Immediate deployment
  • ❌ May trigger unwanted deployment
  • ❌ No gradual rollout
Use when: Using standard deployments without versions.

Version Deployment Secrets

wrangler versions secret put API_KEY
# Then: wrangler versions deploy
  • ✅ No accidental deployment
  • ✅ Can test before deploying
  • ✅ Gradual rollout support
  • ❌ Two-step process
Use when: Using gradual rollouts with versions.

Secret Deployment Behavior

Standard Deployment

With standard deployments, wrangler secret put may trigger a deployment:
// From secret/index.ts:184-197
try {
  return await fetchResult(config, url, {
    method: "PUT",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      name: args.key,
      text: secretValue,
      type: "secret_text",
    }),
  });
} catch (e) {
  if (e instanceof APIError && e.code === VERSION_NOT_DEPLOYED_ERR_CODE) {
    throw new UserError(
      "Secret edit failed. You attempted to modify a secret, but the latest version isn't currently deployed."
    );
  }
}
With standard deployments, adding a secret when the latest version isn’t deployed will fail. You must either:
  1. Deploy the latest version first, then add secrets
  2. Use wrangler versions secret put instead

Version Deployment

With version deployments:
  • Secrets create a new version
  • No automatic deployment
  • Explicit wrangler versions deploy required

Accessing Secrets in Code

Secrets are available as environment variables:
export default {
  async fetch(request, env) {
    // Access secret from env
    const apiKey = env.API_KEY;
    
    // Use in API calls
    const response = await fetch('https://api.example.com', {
      headers: {
        'Authorization': `Bearer ${apiKey}`
      }
    });
    
    return response;
  }
}

TypeScript Types

Define environment types:
interface Env {
  API_KEY: string;
  DATABASE_URL: string;
  STRIPE_KEY: string;
}

export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    // Now env.API_KEY is typed
    const key = env.API_KEY;
    // ...
  }
}

Secret Inheritance

When uploading a new version, secrets are inherited from the previous version by default:
// From versions/upload.ts:710-711
keepVars: props.keepVars ?? false,
keepSecrets: true, // secrets are always inherited
This means:
  • New versions automatically include existing secrets
  • You only need to update changed secrets
  • Deleted secrets require explicit removal

Secret Security

Best Practices

  1. Never commit secrets - Use .gitignore for secret files
  2. Rotate regularly - Update secrets periodically
  3. Use least privilege - Only grant necessary permissions
  4. Audit secret access - Monitor which Workers use which secrets
  5. Delete unused secrets - Remove secrets no longer needed

Secret Storage

Secrets are:
  • ✅ Encrypted at rest
  • ✅ Encrypted in transit
  • ✅ Never logged
  • ✅ Never returned by API
  • ✅ Isolated per Worker

What NOT to Use Secrets For

Configuration values - Use environment variables instead ❌ Public API keys - Use plain environment variables ❌ Non-sensitive data - Use environment variables or KV ❌ Large data - Use KV, R2, or D1

Environment-Specific Secrets

For Workers with environments:
# Production secrets
wrangler secret put API_KEY --env production

# Staging secrets  
wrangler secret put API_KEY --env staging

# Development secrets
wrangler secret put API_KEY --env dev
Each environment has isolated secrets.

Troubleshooting

Worker Not Found

If the Worker doesn’t exist:
 Worker "my-worker" not found.
Solution: The CLI will offer to create a draft Worker:
? There doesn't seem to be a Worker called "my-worker". 
  Do you want to create a new Worker with that name and add secrets to it? (Y/n)

Latest Version Not Deployed

When using versions:
 Secret edit failed. You attempted to modify a secret, but the latest 
   version of your Worker isn't currently deployed.
Solution: Use version-specific commands:
wrangler versions secret put API_KEY

Bulk Upload Failures

If bulk upload fails:
 The contents of "secrets.json" is not valid JSON
Solution: Validate JSON format or use .env format instead.

Next Steps

Deploying

Deploy Workers with secrets

Versioning

Manage Worker versions

Build docs developers (and LLMs) love