Environment Variables
Environment variables let you store secrets and configuration outside your codebase. They are encrypted at rest, decrypted at deploy time, and injected into your API routes as environment variables.
Setting variables
Section titled “Setting variables”Via the CLI
Section titled “Via the CLI”# Set a variabletma env set DATABASE_URL=postgres://...
# Set another variabletma env set ANALYTICS_KEY=ak_123
# List all variablestma env list
# Remove a variabletma env remove DATABASE_URL
# Pull variable names to a local .env.local file (values are redacted)tma env pullThe CLI sets variables for the production environment. Environment scoping beyond production (preview, development) is managed through the dashboard.
Via the dashboard
Section titled “Via the dashboard”Navigate to your project’s Settings > Environment Variables page. Add, edit, or remove variables from the web interface. Changes take effect on the next deployment.
Environment scoping
Section titled “Environment scoping”Every secret in TMA.sh is scoped to a specific environment:
| Scope | Applied to |
|---|---|
| Production | Production deployments |
| Preview | Preview deployments |
| Development | Reserved for development-scoped workflows |
Environment scoping is managed through the dashboard at Settings > Environment Variables. The CLI (tma env set) always writes production secrets. Deployment builds currently load production or preview scoped secrets based on deployment type.
Accessing variables in API routes
Section titled “Accessing variables in API routes”Environment variables are available via the env parameter in your API routes. With Hono, access them on c.env; with plain fetch handlers, they are the second argument to fetch(request, env):
import { Hono } from 'hono';
type Env = { Bindings: { DATABASE_URL: string; STRIPE_SECRET_KEY: string; RESEND_API_KEY: string; };};
const app = new Hono<Env>();
app.post('/api/send-email', async (c) => { const res = await fetch('https://api.resend.com/emails', { method: 'POST', headers: { Authorization: `Bearer ${c.env.RESEND_API_KEY}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ subject: 'Hello', text: 'Welcome to the app!', }), });
return c.json({ sent: res.ok });});
export default app;Local development
Section titled “Local development”tma dev does not automatically pull dashboard secrets into local worker bindings. For local frontend code, Vite’s standard .env loading still applies:
# .env (or .env.local)DATABASE_URL=postgres://localhost:5432/myappSTRIPE_SECRET_KEY=sk_test_...For worker-side local values, provide explicit test values in your local code/test setup.
Common use cases
Section titled “Common use cases”| Variable | Purpose |
|---|---|
DATABASE_URL | External database connection string |
BOT_TOKEN | Telegram bot token |
STRIPE_SECRET_KEY | Payment processor credentials |
RESEND_API_KEY | Email service API key |
SENTRY_DSN | Error tracking |
Security
Section titled “Security”- Variables are encrypted at rest and only decrypted during deployment.
- They are never included in your static SPA bundle — only API routes have access.
- Avoid logging secret values from your own build scripts or runtime code.
- Use the dashboard or CLI to rotate secrets without changing code — trigger a redeploy to pick up the new value.
- Add
.envto your.gitignoreto prevent accidentally committing local secrets.