SPFx Environment Variables: A Practical Guide for Every Scenario

If you’ve ever worked on an SPFx project with a team, you’ve probably hit this problem: your serve.json has your personal SharePoint URL hardcoded, your colleague has a different tenant, and every time someone pulls the latest code from Git, something breaks. Sound familiar?

That’s exactly what environment variables fix. And once you understand how they work in SPFx, you’ll wonder how you ever managed without them.

Let me walk you through everything, from the simplest one-liner fix to more advanced patterns for build-time config injection, so you can pick the approach that fits your project.

Let me explain how to use SharePoint Framework (SPFx) environment variables.

What Are SPFx Environment Variables, Really?

Think of environment variables as settings that live outside your code. Instead of hardcoding https://mytenant.sharepoint.com directly in a config file or TypeScript file, you store it in your operating system (or a .env file), and your project just reads it from there.

This is incredibly useful when:

  • You work in a team where everyone has a different SharePoint tenant URL
  • You deploy to multiple environments — dev, staging, production
  • You don’t want API keys or secrets accidentally committed to your Git repo
  • You want your CI/CD pipeline to inject the right values at build time

If you want to learn SPFx development, check out our SPFx training course.

Method 1: The SPFX_SERVE_TENANT_DOMAIN Built-In Variable

This is the easiest method and requires zero npm packages. Microsoft built it directly into the SPFx toolchain.

Here’s how it works: when you run gulp serve or heft start, the build system looks for an OS environment variable called SPFX_SERVE_TENANT_DOMAIN and swaps out the {tenantDomain} placeholder in your serve.json automatically.

Step 1 — Update your serve.json

Open config/serve.json and use the placeholder instead of a hardcoded URL:

{
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/serve.schema.json",
"port": 4321,
"https": true,
"initialPage": "https://{tenantDomain}/sites/mysite/_layouts/workbench.aspx"
}
environment variables in spfx

Notice {tenantDomain} — The toolchain will replace this at runtime with the value of your OS environment variable.

Step 2 — Set the environment variable

On Windows (PowerShell) — temporary for this session only:

$env:SPFX_SERVE_TENANT_DOMAIN = "contoso.sharepoint.com"

To make it permanent (so you don’t have to set it every time):

[System.Environment]::SetEnvironmentVariable("SPFX_SERVE_TENANT_DOMAIN", "contoso.sharepoint.com", "User")

Here, update ‘contoso‘ with your tenant domain name.

Environment Variables with SPFx

After some time, reopen your SPFx web part Terminal pane, and check whether the variable contains a value or not with this command echo $env:SPFX_SERVE_TENANT_DOMAIN

On macOS / Linux:

export SPFX_SERVE_TENANT_DOMAIN="contoso.sharepoint.com"

To persist it, add that line to your ~/.bashrc or ~/.zshrc.

Step 3 — Run gulp serve as usual

That’s it. The build toolchain replaces {tenantDomain} on the fly before opening the browser. No code changes, no config file edits per developer.

NOTE: If you are using SPFx solutions with gulp packages, then run the gulp serve command to test. In case you are on SPFx version 1.22.*, with the Heft-based tool chain, run the Heft start command.

In the image below, you can see that the initialPage URL in serve.json has a placeholder called {tenantDomain}. Now, instead of hardcoding our SharePoint tenant domain directly in this URL, we created an environment variable called SPFX_SERVE_TENANT_DOMAIN and stored our tenant domain value in it.

So now when we run the heft start command, SPFx is smart enough to automatically read that environment variable and replace the {tenantDomain} placeholder with our actual tenant domain in the URL.

using environment variables in sharepoint framework

Why I like this method: It’s the simplest possible solution for the most common pain point. It works out of the box with every SPFx version and requires no extra packages. The downside is it only handles the tenant domain/URL for serve.json — it doesn’t help with injecting variables into your TypeScript code.

Method 2: OS Environment Variables in PowerShell (Quick and Practical)

Sometimes you just need to pass a value temporarily before running a command. PowerShell makes this very easy.

Temporary (session only):

$env:SPFX_API_KEY = "my-api-key-12345"
gulp serve

This variable exists only until you close the terminal window.

Permanent (User scope):

[System.Environment]::SetEnvironmentVariable("SPFX_API_KEY", "my-api-key-12345", "User")

Permanent (Machine scope — needs admin):

[System.Environment]::SetEnvironmentVariable("SPFX_API_KEY", "my-api-key-12345", "Machine")

Check if it’s set:

$env:SPFX_API_KEY
using environment variables in sharepoint framework for current session

Remove it:

# From session only
Remove-Item Env:\SPFX_API_KEY

# Permanently (User scope)
[System.Environment]::SetEnvironmentVariable("SPFX_API_KEY", $null, "User")
remove environment variables in spfx for current session

In the image above, you can see I removed the current session variable. When I check back, does it still contain a value? It shows empty.

This method is great for quick local testing. But if you’re working in a team, it gets tedious; every developer has to remember to set all the right variables manually. That’s where .env files come in.

Method 3: Using .env Files with dotenv (Recommended for Teams)

This is my favorite approach for team projects with the gulp-based toolchain (SPFx versions before 1.22). You define all your variables in a .env file, exclude them from version control, and dynamically inject them into Webpack during the build process.

Step 1 —  Install Required Packages

Install dotenv to read the files, and cross-env to easily switch environments across Windows, Mac, and CI/CD pipelines.

npm install dotenv cross-env --save-dev

Step 2 — Create your Environment Files

Create a folder named env at the root of your project, and create a file named dev.env (for local development) and prod.env (for production).

# env/dev.env
SPFX_API_BASE_URL=https://dev-api.contoso.com
SPFX_APP_INSIGHTS_KEY=dev-abc123-xyz
SPFX_ENVIRONMENT=development
setup environment variables in spfx web part

Step 3 — Add environment files to .gitignore

This is critical. You never want to commit API keys or production secrets to your repository.

# .gitignore
env/*.env
!env/sample.env
spfx development environment variables setup

(Tip: It is good practice to commit a sample.env with dummy values so new developers know what variables they need to configure).

Step 4 — Update gulpfile.js for Dynamic Injection

Instead of hardcoding every variable, we can configure Webpack to automatically find and inject any variable that starts with SPFX_. Replace your gulpfile.js with this:

'use strict';

const gulp = require('gulp');
const build = require('@microsoft/sp-build-web');
const webpack = require('webpack');
const dotenv = require('dotenv');
const fs = require('fs');
const path = require('path');

// 1. Determine the environment (default to 'dev')
const environment = process.env.NODE_ENV || 'dev';
const envPath = path.resolve(__dirname, `./env/${environment}.env`);

// 2. Load the file if it exists
let envConfig = {};
if (fs.existsSync(envPath)) {
envConfig = dotenv.config({ path: envPath }).parsed || {};
} else {
console.warn(`[!] Warning: ${envPath} not found. Proceeding without env injection.`);
}

// 3. Merge into Webpack configuration
build.configureWebpack.mergeConfig({
additionalConfiguration: (generatedConfig) => {
const definitions = {};

Object.keys(envConfig).forEach((key) => {
if (key.startsWith('SPFX_')) {
definitions[`process.env.${key}`] = JSON.stringify(envConfig[key]);
}
});

generatedConfig.plugins.push(new webpack.DefinePlugin(definitions));
return generatedConfig;
}
});

// 4. Force task registration (Fixes "Task never defined" error)
let getTasks = build.rig.getTasks;
build.rig.getTasks = function () {
let result = getTasks.call(build.rig);
// Ensure serve is properly mapped
result.set('serve', result.get('serve-deprecated') || result.get('serve'));
return result;
};

// 5. Initialize build
build.initialize(gulp);

Step 5 — Add TypeScript Typings (Crucial for SPFx)

To prevent TypeScript from complaining that process.env is undefined, create or update a global.d.ts file inside your src folder:

// src/global.d.ts
declare interface NodeJS {
env: {
NODE_ENV: 'dev' | 'prod';
SPFX_API_BASE_URL: string;
SPFX_APP_INSIGHTS_KEY: string;
SPFX_ENVIRONMENT: string;
[key: string]: string | undefined;
};
}
declare var process: NodeJS;
spfx environment variables setup in gulp tool chain

Step 6 — Use Variables in your TypeScript/React Code

Because of the typings in Step 5, you no longer need as string. You get full IntelliSense autocomplete natively in VS Code.

import * as React from 'react';
import styles from './Listitems.module.scss';
import type { IListitemsProps } from './IListitemsProps';
import { escape } from '@microsoft/sp-lodash-subset';

export default class Listitems extends React.Component<IListitemsProps> {
public render(): React.ReactElement<IListitemsProps> {
const {
description,
isDarkTheme,
environmentMessage,
hasTeamsContext,
userDisplayName
} = this.props;

return (
<section className={`${styles.listitems} ${hasTeamsContext ? styles.teams : ''}`}>
<div className={styles.welcome}>
<img alt="" src={isDarkTheme ? require('../assets/welcome-dark.png') : require('../assets/welcome-light.png')} className={styles.welcomeImage} />
<h2>Well done, {escape(userDisplayName)}!</h2>
<div>{environmentMessage}</div>
<div>Web part property value: <strong>{escape(description)}</strong></div>
</div>

{/* --- CUSTOM ENVIRONMENT VARIABLES BLOCK --- */}
<div style={{ backgroundColor: '#e1dfdd', padding: '15px', margin: '20px 0', borderRadius: '4px', color: '#323130' }}>
<h3 style={{ marginTop: 0 }}>Environment Variables Test</h3>
<p style={{ margin: '5px 0' }}><strong>Environment:</strong> {process.env.SPFX_ENVIRONMENT}</p>
<p style={{ margin: '5px 0' }}><strong>API Base URL:</strong> {process.env.SPFX_API_BASE_URL}</p>
<p style={{ margin: '5px 0' }}><strong>App Insights Key:</strong> {process.env.SPFX_APP_INSIGHTS_KEY}</p>
</div>
{/* ---------------------------------------- */}
</section>
);
}
}

Step 7 — Running and Building for Different Environments
Now, you can easily switch between environments using cross-env. You can add these commands directly to the "scripts" section of your package.json:

"scripts": {
  "serve": "cross-env NODE_ENV=dev gulp serve",
  "build:prod": "cross-env NODE_ENV=prod gulp bundle --ship && gulp package-solution --ship"
}

Then run the gulp serve command. In the SPFx web part, you’ll see the environment variables we defined:

configure environment variables for spfx with gulp package tool chain

Method 4: Webpack DefinePlugin — Injecting Variables at Build Time

The Webpack DefinePlugin is the engine behind method 3. Sometimes it’s useful to understand it directly, especially for CI/CD pipelines where you want the pipeline itself to inject values.

Here’s a standalone example:

// gulpfile.js
build.configureWebpack.mergeConfig({
additionalConfiguration: (generatedConfig) => {
const isDebugMode = generatedConfig.plugins
.find(p => p instanceof webpack.DefinePlugin)
?.definitions?.DEBUG;

generatedConfig.plugins.find(p => p instanceof webpack.DefinePlugin)
.definitions['process.env.API_ENDPOINT'] = isDebugMode
? JSON.stringify('https://dev-api.contoso.com')
: JSON.stringify(process.env.API_ENDPOINT);

return generatedConfig;
}
});

In this case, during gulp serve (debug mode), it uses the dev URL. During gulp bundle --ship (production), it reads from the OS environment variable API_ENDPOINT — which in a DevOps pipeline you’d set as a secret pipeline variable.

This is perfect for things like:

  • Azure Application Insights instrumentation keys
  • Backend API endpoints
  • Feature flags

Method 5: SPFx 1.22+ with Heft (New Build System)

If you’re starting a fresh project with SPFx 1.22 or later, you’ll notice that gulp has been replaced by Heft as the default build system. Heft is a config-driven task orchestrator from Rush Stack that Microsoft now recommends for all new projects.

With Heft, you can’t use gulpfile.js the same way. Here are the main approaches.

Microsoft’s official way to customize WebPack in Heft projects is through a webpack-patch.json config file.

Step 1: Create the JSON Configuration File

In your project’s config folder, create a new file named webpack-patch.json and add the following content:

{
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/webpack-patch.schema.json",
"patchFiles": ["./config/webpack-patch/env-inject.js"]
}
Environment variables in a React SharePoint Webpart

Step 2: Create the JavaScript Patch File

You need to create a new folder inside the config folder to hold your script.

  • Create a folder named webpack-patch inside the config folder.
  • Inside that new folder, create a file named env-inject.js and add this code:
const dotenv = require('dotenv');
const fs = require('fs');
const path = require('path');

// Resolve webpack from your project root — no rig path guessing needed
const webpackPath = require.resolve('webpack', { paths: [path.resolve(__dirname, '../../')] });
const webpack = require(webpackPath);

module.exports = function(webpackConfig) {
const NODE_ENV = process.env.NODE_ENV || 'dev';
const envPath = path.resolve(__dirname, `../../env/${NODE_ENV}.env`);

if (!fs.existsSync(envPath)) {
console.warn(`Warning: ${envPath} not found. Skipping env injection.`);
return webpackConfig;
}

const envConfig = dotenv.parse(fs.readFileSync(envPath));
const definitions = {};

Object.entries(envConfig)
.filter(([key]) => key.startsWith('SPFX_'))
.forEach(([key, value]) => {
definitions[`process.env.${key}`] = JSON.stringify(value);
});

webpackConfig.plugins.push(new webpack.DefinePlugin(definitions));
return webpackConfig;
};
spfx environment variables setup step by step

Step 3: Install Dependencies and Create Environment Files

Your patch file uses dotenv to parse environment files, so you need to install it.

  • Open your terminal and run:
npm install dotenv --save-dev
  • In the root of your project (the same level as src and config), create a folder named env.
  • Inside the env folder, create a file named dev.env and define your variables. They must start with SPFX_:
SPFX_API_BASE_URL=https://my-dev-api.azurewebsites.net
SPFX_GREETING_MESSAGE=Hello from Dev Environment
environment variables setup in windows 10 for spfx

Step 4: Add TypeScript Typings (Crucial for SPFx)
By default, TypeScript will throw an error if you try to type process.env.SPFX_API_BASE_URL because it doesn’t know what process.env is. You need to define these types so your SPFx web part compiles successfully.

  • In your src folder, create a file named global.d.ts (or open it if it already exists).
  • Add the following TypeScript declaration to let the compiler know about your custom variables:
declare interface NodeJS {
  env: {
    NODE_ENV: 'dev' | 'production';
    SPFX_API_BASE_URL: string;
    SPFX_GREETING_MESSAGE: string;
    [key: string]: string | undefined;
  };
}
declare var process: NodeJS;
Configuring Environment Variables for SPFx Development

Step 5: How to Test and Use It

Now you can safely use your environment variables anywhere inside your web part component (e.g., inside src/webparts/yourWebPart/components/YourWebPart.tsx).

  • Open your main React component or web part TypeScript file.
  • Add a simple HTML tags to render it on the screen:
public render(): React.ReactElement<IHeftProjectProps> {
    const {
      description,
      isDarkTheme,
      environmentMessage,
      hasTeamsContext,
      userDisplayName
    } = this.props;

    return (
      <section className={`${styles.heftProject} ${hasTeamsContext ? styles.teams : ''}`}>
        <div className={styles.welcome}>
          <img alt="" src={isDarkTheme ? require('../assets/welcome-dark.png') : require('../assets/welcome-light.png')} className={styles.welcomeImage} />
          <h2>Well done, {escape(userDisplayName)}!</h2>
          <div>{environmentMessage}</div>
          <div>Web part property value: <strong>{escape(description)}</strong></div>
        </div>
        
        {/* --- CUSTOM ENVIRONMENT VARIABLES BLOCK --- */}
        <div style={{ backgroundColor: '#f4f4f4', padding: '15px', marginTop: '20px', borderRadius: '4px' }}>
          <h3>Environment Variables Test</h3>
          <p><strong>Greeting:</strong> {process.env.SPFX_GREETING_MESSAGE}</p>
          <p><strong>API Base URL:</strong> {process.env.SPFX_API_BASE_URL}</p>
        </div>
        {/* ---------------------------------------- */}

      </section>
    );
  }

To test the dev.env variables, simply run your standard Heft command. Because the script defaults to dev, it will automatically pick up dev.env

heft start
access environment variables within spfx web part

Note: If you want to test a production environment (e.g., prod.env), you would run cross-env NODE_ENV=prod npm run build (assuming you have cross-env installed and configured in your package.json scripts)

Alternative: Pre-Build TypeScript Generation (Type-Safe)

If you want full TypeScript type safety and IDE autocomplete on your environment variables, this is the cleanest approach.

Create scripts/generate-env-config.js:

const fs = require('fs');
const dotenv = require('dotenv');

const NODE_ENV = process.env.NODE_ENV || 'dev';
const envConfig = dotenv.parse(fs.readFileSync(`./env/${NODE_ENV}.env`));

const spfxVars = Object.entries(envConfig)
.filter(([key]) => key.startsWith('SPFX_'))
.map(([key, value]) => ` ${key}: '${value}'`)
.join(',\n');

const output = `// AUTO-GENERATED — Do not edit manually
export const ENV_CONFIG = {
${spfxVars}
} as const;
`;

fs.writeFileSync('src/utils/envConfig.generated.ts', output);
console.log('Environment config generated successfully.');

Update package.json scripts:

"scripts": {
"generate-env": "node scripts/generate-env-config.js",
"start:dev": "cross-env NODE_ENV=dev npm run generate-env && heft start",
"start:prod": "cross-env NODE_ENV=prod npm run generate-env && heft start"
}

Use it in your components:

import { ENV_CONFIG } from '../utils/envConfig.generated';

// Full autocomplete and type checking!
const apiUrl = ENV_CONFIG.SPFX_API_BASE_URL;
const timeout = ENV_CONFIG.SPFX_API_TIMEOUT;

The generated file is committed to .gitignore and rebuilt every time you run the start script.

Choosing the Right Method

Here’s a quick summary of when to use what:

ScenarioBest Method
Just need the right tenant URL for gulp serveSPFX_SERVE_TENANT_DOMAIN OS variable
Quick local testing, no team coordination neededPowerShell $env: temporary variable
Team project on gulp-based SPFx (before v1.22).env files + dotenv + DefinePlugin in gulpfile.js
CI/CD pipeline injecting secretsOS env variables + DefinePlugin in gulpfile.js or webpack-patch
New project on SPFx 1.22+ with HeftWebpack Patch method (webpack-patch.json)
Need type safety and autocompletePre-Build TypeScript generation script

Common Mistakes to Avoid

  • Committing .env files to Git. Always add .env*.env, and env/ to .gitignore.
  • Forgetting the SPFX_ prefix. Variables without this prefix often end up as undefined at runtime.
  • Using require('webpack') in Heft projects. This causes silent build failures. Use the SPFx rig import instead.
  • Not providing fallback values. Always use process.env.MY_VAR || 'fallback' so your build doesn’t break in environments where the variable isn’t set.
  • Expecting runtime env access. Remember — DefinePlugin replaces values at build time, not runtime. The browser never sees process.env; it only sees the already-substituted string.

Conclusion

I hope you found this article helpful. In this guide, I explained different ways to use environment variables in SPFx. I covered built-in variables, PowerShell options, .env files, and Webpack methods. I also shared recommended approaches and alternatives for different scenarios.

If you are working on SPFx projects, choose the method that fits your setup and needs. Start with simple options and move to advanced ones as required. Using environment variables will make your solution more flexible and easier to manage.

Also, you may like:

Power Apps functions free pdf

30 Power Apps Functions

This free guide walks you through the 30 most-used Power Apps functions with real business examples, exact syntax, and results you can see.

Download User registration canvas app

DOWNLOAD USER REGISTRATION POWER APPS CANVAS APP

Download a fully functional Power Apps Canvas App (with Power Automate): User Registration App