Cursor can accidentally hardcode staging URLs into production code because it lacks awareness of your deployment context. By adding environment-aware rules to your .cursorrules file and using .env files with explicit Cursor prompts, you ensure generated code always references the correct endpoints for each environment.
Why Cursor leaks staging endpoints into production code
When Cursor generates API integration code, it often inserts the first URL it finds in your project context. If your codebase contains staging URLs in config files, test fixtures, or comments, Cursor may copy those into production code paths. This tutorial shows you how to set up environment-based configuration that Cursor respects, preventing accidental staging URL leakage into production builds.
Prerequisites
- Cursor installed and a project open
- Basic understanding of environment variables (.env files)
- A project with at least one API integration
- Familiarity with Cmd+K and Cmd+L shortcuts in Cursor
Step-by-step guide
Create an environment config module
Create an environment config module
Start by creating a centralized configuration file that reads API endpoints from environment variables. This gives Cursor a single reference point for all URLs. Open Cursor and create a new file at src/config/env.ts. Type or paste the following code. This pattern ensures no hardcoded URLs exist anywhere in your codebase.
1// src/config/env.ts2const requiredEnvVars = [3 'API_BASE_URL',4 'AUTH_SERVICE_URL',5 'STORAGE_URL',6] as const;78type EnvVar = (typeof requiredEnvVars)[number];910function getEnv(key: EnvVar): string {11 const value = process.env[key];12 if (!value) {13 throw new Error(`Missing required environment variable: ${key}`);14 }15 return value;16}1718export const config = {19 apiBaseUrl: getEnv('API_BASE_URL'),20 authServiceUrl: getEnv('AUTH_SERVICE_URL'),21 storageUrl: getEnv('STORAGE_URL'),22} as const;Pro tip: Use 'as const' assertions to make your config object deeply readonly, preventing accidental mutation.
Expected result: A typed configuration module that throws at startup if any required environment variable is missing.
Add environment rules to .cursorrules
Add environment rules to .cursorrules
Create or update your .cursorrules file in the project root to instruct Cursor to always use the config module instead of hardcoding URLs. This rule is included in every Cursor AI prompt automatically, so the AI will follow it in Chat (Cmd+L), Composer (Cmd+I), and inline edits (Cmd+K).
1# .cursorrules23## Environment Variables4- NEVER hardcode API URLs, database connection strings, or service endpoints5- ALWAYS import from src/config/env.ts using: import { config } from '@/config/env'6- Use config.apiBaseUrl, config.authServiceUrl, config.storageUrl7- NEVER reference .env files directly in application code8- NEVER use process.env directly outside of src/config/env.ts910## API Integrations11- All HTTP clients must use config.apiBaseUrl as the base URL12- Authentication endpoints use config.authServiceUrl13- File upload endpoints use config.storageUrlExpected result: Cursor will now reference config imports instead of hardcoded URLs in all generated code.
Create per-environment .env files
Create per-environment .env files
Set up separate .env files for each environment. Add all of them to .cursorignore so Cursor does not index the actual secret values, but keep a .env.example checked into Git so Cursor knows the variable names. Open Cmd+K in the terminal and type: 'create .env.example, .env.development, and .env.production files with the three required API endpoint variables'.
1# .env.example (committed to Git)2API_BASE_URL=https://api.example.com3AUTH_SERVICE_URL=https://auth.example.com4STORAGE_URL=https://storage.example.com56# .env.development (NOT committed)7API_BASE_URL=http://localhost:30018AUTH_SERVICE_URL=http://localhost:30029STORAGE_URL=http://localhost:30031011# .env.production (NOT committed)12API_BASE_URL=https://api.myapp.com13AUTH_SERVICE_URL=https://auth.myapp.com14STORAGE_URL=https://storage.myapp.comPro tip: Add .env.development, .env.production, and .env.local to both .gitignore and .cursorignore to prevent secrets from leaking into AI context or version control.
Expected result: Three environment files with correct URLs for each deployment target, with only the example file in version control.
Configure .cursorignore to protect secrets
Configure .cursorignore to protect secrets
Create a .cursorignore file to prevent Cursor from indexing your actual environment files. This ensures the AI never sees real API keys or staging URLs, eliminating the risk of them appearing in generated code.
1# .cursorignore2.env3.env.*4!.env.example5*.pem6*.key7credentials/8secrets/Expected result: Cursor's codebase indexing will skip all .env files except .env.example, keeping real endpoints out of AI context.
Prompt Cursor to generate environment-safe API code
Prompt Cursor to generate environment-safe API code
Now test the setup by prompting Cursor to generate an API integration. Open Chat with Cmd+L, reference your config file with @file, and ask it to create a new service. The AI should use your config module automatically thanks to the .cursorrules directive.
1// Example prompt in Cursor Chat (Cmd+L):2// @src/config/env.ts Create a UserService class that fetches3// user profiles from the /users endpoint and handles errors.4// Use our project's config module for the base URL.56// Expected generated output:7import { config } from '@/config/env';89export class UserService {10 private baseUrl = config.apiBaseUrl;1112 async getProfile(userId: string) {13 const response = await fetch(`${this.baseUrl}/users/${userId}`);14 if (!response.ok) {15 throw new Error(`Failed to fetch user ${userId}: ${response.status}`);16 }17 return response.json();18 }19}Pro tip: Always include @src/config/env.ts in your Cursor prompt when generating API code. This gives the AI direct context about your configuration pattern.
Expected result: Cursor generates a service class that imports from your config module with zero hardcoded URLs.
Audit existing code for hardcoded endpoints
Audit existing code for hardcoded endpoints
Use Cursor's Ask mode (Cmd+L) to scan your codebase for any remaining hardcoded URLs. Type the following prompt to get a comprehensive audit. Fix any findings by replacing hardcoded values with config imports.
1// Prompt for Cursor Chat (Cmd+L):2// @codebase Search for any hardcoded URLs that contain3// 'localhost', 'staging', 'dev.', or 'api.' in the src/4// directory. List each file, line number, and the hardcoded5// URL. Suggest how to replace each one with the appropriate6// config import from @/config/env.Expected result: Cursor returns a list of any hardcoded URLs in your source code with suggested replacements using your config module.
Complete working example
1// src/config/env.ts2// Centralized environment configuration — the single source3// of truth for all API endpoints and service URLs.45const requiredEnvVars = [6 'API_BASE_URL',7 'AUTH_SERVICE_URL',8 'STORAGE_URL',9 'DATABASE_URL',10 'REDIS_URL',11] as const;1213type EnvVar = (typeof requiredEnvVars)[number];1415function getEnv(key: EnvVar): string {16 const value = process.env[key];17 if (!value) {18 throw new Error(19 `Missing required environment variable: ${key}. ` +20 `Check your .env.${process.env.NODE_ENV || 'development'} file.`21 );22 }23 return value;24}2526function getOptionalEnv(key: string, fallback: string): string {27 return process.env[key] || fallback;28}2930export const config = {31 env: getOptionalEnv('NODE_ENV', 'development'),32 apiBaseUrl: getEnv('API_BASE_URL'),33 authServiceUrl: getEnv('AUTH_SERVICE_URL'),34 storageUrl: getEnv('STORAGE_URL'),35 databaseUrl: getEnv('DATABASE_URL'),36 redisUrl: getEnv('REDIS_URL'),37 isProduction: process.env.NODE_ENV === 'production',38 isDevelopment: process.env.NODE_ENV !== 'production',39} as const;4041// Validate all required vars at import time42if (config.isProduction) {43 requiredEnvVars.forEach((key) => getEnv(key));44}Common mistakes when preventing environment leakage in Cursor output
Why it's a problem: Putting real staging URLs in .env.example
How to avoid: Use clearly fake placeholder URLs in .env.example like https://api.example.com. Cursor will see the pattern without learning real endpoints.
Why it's a problem: Forgetting to add .env files to .cursorignore
How to avoid: Add .env and .env.* to .cursorignore immediately after creating your environment files.
Why it's a problem: Using process.env directly throughout the codebase
How to avoid: Restrict all process.env access to a single config file and add a .cursorrules directive forbidding direct process.env usage elsewhere.
Why it's a problem: Not referencing the config file in Cursor prompts
How to avoid: Always include @src/config/env.ts when prompting Cursor to generate code that involves API calls or service endpoints.
Best practices
- Keep all environment-dependent values in a single config module that Cursor can reference with @file
- Add .cursorrules directives that explicitly forbid hardcoded URLs and mandate config imports
- Use .cursorignore to prevent Cursor from indexing files containing real secrets or staging URLs
- Commit a .env.example with fake placeholder URLs so Cursor understands the variable naming pattern
- Start new Cursor Chat sessions (Cmd+L) when switching between tasks to prevent context pollution
- Review all Cursor-generated code for hardcoded strings before committing, especially in fetch calls and HTTP clients
- Use Cursor's @codebase search to periodically audit for accidental hardcoded endpoints
Still stuck?
Copy one of these prompts to get a personalized, step-by-step explanation.
I have a TypeScript project with separate staging and production environments. Write a centralized environment configuration module that reads API_BASE_URL, AUTH_SERVICE_URL, and STORAGE_URL from environment variables, validates they exist at startup, and exports a typed config object. Include .env.example and .env.development templates.
In Cursor Chat (Cmd+L): @src/config/env.ts @.cursorrules Generate a new API service class for the /orders endpoint. Use our config module for the base URL. Never hardcode any URLs. Follow the environment variable rules in .cursorrules.
Frequently asked questions
Does Cursor send my .env file contents to its servers?
Cursor sends file contents to AI model providers when you reference them or when they are included in the codebase index. Adding .env files to .cursorignore prevents indexing. Enable Privacy Mode in Cursor Settings for additional protection, ensuring no data is stored after processing.
Will .cursorrules prevent Cursor Tab completion from suggesting hardcoded URLs?
No. The .cursorrules file only applies to Chat (Cmd+L), Composer (Cmd+I), and inline edits (Cmd+K). Tab completion uses a separate model that does not read .cursorrules. You must manually reject Tab suggestions that contain hardcoded URLs.
How do I handle environment variables in a monorepo with Cursor?
Place a .cursorignore in the root to exclude all .env files globally. Use nested .cursor/rules/ directories in each package to define package-specific config module paths. When prompting Cursor, reference the specific package config with @packages/api/src/config/env.ts.
Can Cursor automatically detect that I am in a development vs production environment?
Cursor has no awareness of your deployment environment. It only sees file contents and your prompts. You must explicitly tell Cursor which environment context matters by referencing the appropriate config files and stating your intent in the prompt.
What if Cursor still hardcodes URLs despite my .cursorrules?
Rules can be forgotten in long sessions. Start a new Chat session with Cmd+N, paste the relevant rule directly into your first message, and reference @src/config/env.ts explicitly. If the issue persists, try switching models in the dropdown from the chat panel.
Should I use .cursor/rules/ files instead of .cursorrules for environment rules?
Yes. The newer .cursor/rules/ directory with .mdc files is recommended over the legacy .cursorrules file. Create a file like .cursor/rules/env-config.mdc with 'alwaysApply: true' so the environment rules are automatically included in every prompt.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation