# How to Secure API Keys in Retool

- Tool: Retool
- Difficulty: Intermediate
- Time required: 15-20 min
- Compatibility: Retool Cloud and Self-hosted
- Last updated: March 2026

## TL;DR

Never put API keys directly in Retool queries or component values — they will be visible in the app definition and network requests. Instead, store keys as environment/configuration variables (Settings → Configuration Variables) or as resource credentials. Access them in queries via {{ retoolContext.configVars.API_KEY }} or through the encrypted resource connection. Mark them as secret to prevent exposure in the UI.

## Protecting Secrets and Credentials in Retool

API keys and credentials embedded directly in Retool queries, component values, or JavaScript code are a security risk. They can be viewed by anyone with edit access to the app, appear in network request logs, and get committed to source control when using Git sync.

Retool provides two secure storage mechanisms: Configuration Variables (formerly Environment Variables) stored in Settings, and Resource Credentials stored per resource. Both are encrypted at rest. Configuration Variables can be marked as 'secret' to prevent them from appearing in the Retool UI after initial entry. Resource Credentials are never exposed to the frontend.

This tutorial covers creating configuration variables, accessing them in queries, marking them as secrets, and the best practices for API key lifecycle management.

## Before you start

- Admin access to a Retool organization (required to create configuration variables)
- An API key you need to store securely
- Basic understanding of Retool resources and queries
- Retool Cloud or Self-hosted instance

## Step-by-step guide

### 1. Navigate to Settings → Configuration Variables

Log in as an Admin and go to Settings (gear icon top-right or your-retool.com/settings). Find 'Configuration Variables' in the Settings sidebar (may be labeled 'Environment Variables' in older Retool versions). This is where you define key-value pairs that are available across all apps in the organization. Keys are stored encrypted in Retool's backend.

**Expected result:** The Configuration Variables page shows a list of existing variables (if any) with a + button to add new ones.

### 2. Create a new configuration variable and mark it as secret

Click '+ New variable'. Enter a name following SCREAMING_SNAKE_CASE convention (e.g., STRIPE_SECRET_KEY, OPENAI_API_KEY, SENDGRID_API_KEY). Enter the value — the actual API key. Most critically: toggle the 'Secret' checkbox ON. Secret variables are write-only after initial creation — their value is never displayed in the UI again, preventing shoulder-surfing and accidental exposure in screenshots.

```
// Configuration variable naming conventions:
// STRIPE_SECRET_KEY    → Payment API key
// OPENAI_API_KEY       → LLM API key
// SENDGRID_API_KEY     → Email API key
// INTERNAL_API_TOKEN   → Internal service token
// DB_ENCRYPTION_KEY    → For data encryption

// Non-secret variables (safe to display in UI):
// STRIPE_PUBLISHABLE_KEY  → Public key, OK to show
// APP_VERSION             → Version string
// SUPPORT_EMAIL           → Email address
```

**Expected result:** The variable is created and stored. If marked secret, its value shows as '••••••' immediately after saving and cannot be viewed again — only overwritten.

### 3. Access configuration variables in queries

In any SQL or REST query, reference configuration variables using the retoolContext.configVars object. The syntax is {{ retoolContext.configVars.VARIABLE_NAME }}. For REST API queries, use this in the header value, URL, or body fields. For JS Queries, access the same object without {{ }} delimiters.

```
// REST API query headers (e.g., Stripe API call):
// Header: Authorization
// Value: Bearer {{ retoolContext.configVars.STRIPE_SECRET_KEY }}

// REST API query URL with config var:
// https://api.example.com/v1/endpoint?token={{ retoolContext.configVars.API_TOKEN }}

// In a JS Query (no {{ }} needed):
const apiKey = retoolContext.configVars.OPENAI_API_KEY;
const response = await fetch('https://api.openai.com/v1/chat/completions', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${apiKey}`,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({ model: 'gpt-4', messages: [...] })
});
// NOTE: This makes a direct frontend fetch. For server-side calls,
// use a resource query instead (resource handles auth server-side)
```

**Expected result:** The query sends requests with the API key from the configuration variable, not a hardcoded value.

### 4. Store credentials in resource connections for server-side security

The most secure option for API keys is to configure them in a Retool Resource connection (Settings → Resources). When you create a REST API or GraphQL resource, you enter the base URL and auth credentials (API key, OAuth tokens, etc.) in the resource configuration. These credentials are stored encrypted, never sent to the browser, and injected server-side into requests by Retool's backend. This is stronger than configuration variables because the key never reaches the client.

**Expected result:** Queries using the resource automatically include the configured credentials without any key appearing in the browser's network tab.

### 5. Set per-environment values for configuration variables

On Business and Enterprise plans, configuration variables can have different values per environment (staging vs production). Click on a configuration variable and set different values for each environment. This lets you use a test API key in staging and a live key in production — accessed via the same {{ retoolContext.configVars.STRIPE_SECRET_KEY }} reference, with Retool injecting the correct value based on the current environment.

```
// Same variable name, different per-environment values:
// STRIPE_SECRET_KEY:
//   staging  → sk_test_xxxxxxxx
//   production → sk_live_xxxxxxxx

// Query reference stays the same across environments:
// Header: Authorization
// Value: Bearer {{ retoolContext.configVars.STRIPE_SECRET_KEY }}

// Check current environment in app logic:
// {{ retoolContext.environment }}
// → 'staging' or 'production'
```

**Expected result:** The same query uses the test key in staging and the live key in production without any code changes.

### 6. Audit and rotate API keys safely

When rotating an API key, follow this process: create the new key with your API provider, go to Settings → Configuration Variables, find the variable, and click Edit to update its value. Apps that use the variable immediately start using the new key — no app changes or republishing required. If you use resource-level auth, edit the resource's credential settings instead.

**Expected result:** The new API key is in effect for all apps using the variable immediately. The old key can now be safely revoked.

## Complete code example

File: `REST API Resource + JS Query: secureApiCall`

```javascript
// === APPROACH 1: Resource-level credentials (most secure) ===
// Configure in Settings → Resources → Create REST API resource:
// Base URL: https://api.stripe.com
// Auth type: Bearer Token
// Token: sk_live_xxxxxxxxxx (stored encrypted server-side)

// Query: stripeGetCustomer
// Resource: stripe_api (configured above)
// URL: /v1/customers/{{ table1.selectedRow.data.stripe_customer_id }}
// (Credentials injected server-side — never visible in browser)

// === APPROACH 2: Configuration variable (safer than hardcoding) ===
// Configuration variable: SENDGRID_API_KEY (secret)

// JS Query: sendEmail
try {
  const endpoint = 'https://api.sendgrid.com/v3/mail/send';
  const apiKey = retoolContext.configVars.SENDGRID_API_KEY;
  
  if (!apiKey) {
    throw new Error('SENDGRID_API_KEY not configured in Settings → Configuration Variables');
  }
  
  const payload = {
    personalizations: [{ to: [{ email: emailInput.value }] }],
    from: { email: retoolContext.configVars.SENDER_EMAIL || 'noreply@example.com' },
    subject: subjectInput.value,
    content: [{ type: 'text/plain', value: bodyInput.value }]
  };
  
  // NOTE: This runs in browser — use resource query for production
  const response = await fetch(endpoint, {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${apiKey}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify(payload)
  });
  
  if (!response.ok) {
    const error = await response.json();
    throw new Error(error.errors?.[0]?.message || 'Email send failed');
  }
  
  utils.showNotification({ title: 'Email sent', notificationType: 'success' });
  
} catch (err) {
  console.error('Email error:', err);
  utils.showNotification({
    title: 'Email failed',
    description: err.message,
    notificationType: 'error'
  });
}
```

## Common mistakes

- **Putting API keys directly in a query's header or body fields as plain text** — undefined Fix: Move the key to Settings → Configuration Variables, mark it as secret, and reference it with {{ retoolContext.configVars.KEY_NAME }} in the query.
- **Using a configuration variable without marking it as secret, then sharing an app screenshot that reveals the key** — undefined Fix: Mark the toggle 'Secret' when creating configuration variables for any sensitive value. Secret variables show as '••••••' in all Retool UIs.
- **Calling external APIs directly in JS Queries with fetch() and exposing API keys in browser network requests** — undefined Fix: Configure the API as a Retool Resource with credentials entered in the resource settings. Resource auth is injected server-side by Retool, invisible to browser DevTools.
- **Storing configuration variables in different Retool environments but forgetting to set production values** — undefined Fix: After creating a configuration variable, check each environment tab (staging, production) and verify values are set. An unset production value will return undefined, causing query failures.

## Best practices

- Never hardcode API keys in query bodies, component values, or JS Queries — they will be visible in the app definition and to anyone with edit access.
- Mark all API keys and tokens as 'Secret' in Configuration Variables — secret values are write-only and cannot be viewed after entry.
- Prefer resource-level credential storage over configuration variables for any API that supports it — resource credentials are injected server-side and never reach the browser.
- Use per-environment configuration variable values (Business+) to automatically use test keys in staging and live keys in production.
- Rotate API keys on a regular schedule and immediately after any team member with Admin access leaves the organization.
- Audit who has Admin access to Retool regularly — Admins can view and modify Configuration Variable values.
- For self-hosted Retool, set a strong ENCRYPTION_KEY environment variable in your deployment — this encrypts all stored credentials at rest.

## Frequently asked questions

### Can regular users (non-Admins) see secret configuration variable values in Retool?

No. Secret configuration variables are stored encrypted and their values are never displayed in the Retool UI after initial entry — not to Admins, not to Editors, not to Viewers. Even Admins can only overwrite the value, not read it. This is similar to how GitHub Actions secrets work.

### Is it safe to use fetch() with an API key from retoolContext.configVars in a JS Query?

It is safer than hardcoding, but the API key is still sent from the browser and may appear in browser DevTools network tab. For true server-side security, configure the API as a Retool Resource — resource authentication is injected server-side by Retool's backend and never sent to the browser.

### What happens to apps when I update a configuration variable value?

Apps immediately use the new value — no republishing or changes required. The variable is injected into queries at request time, not baked into the app definition. This is why configuration variables are the recommended way to manage keys that need rotation.

---

Source: https://www.rapidevelopers.com/retool-tutorial/how-to-secure-api-keys-in-retool
© RapidDev — https://www.rapidevelopers.com/retool-tutorial/how-to-secure-api-keys-in-retool
