I use environment variables extensively for allowing multi-tenancy configurations. With the release of SPFx 1.22, there is a new guy in town: Heft! Heft offers plenty of options for implementing environment variables. This post explains four possible approaches to use environment variables with Heft.
Table of Contents
- Heft: The New Build System
- Do I Need to Migrate?
- Understanding the Build Pipeline Change
- The Four Approaches
- Comparison Matrix
- Full Code Repository
- Conclusion
Heft: The New Build System
With SPFx 1.22, Microsoft introduced Heft as the new build orchestrator, replacing the gulp-based toolchain. Heft is a config-driven task orchestrator from Rush Stack that provides better customization, improved performance, and active maintenance.
Why did Microsoft make this change?
- The old gulp toolchain had accumulated technical debt and security warnings
- Community feedback since 2020 requested more flexibility and transparency
- Aligning internal Microsoft teams with external developers accelerates feature delivery
- A true plugin architecture enables sustainable, maintainable customizations
For the full details, see Microsoft’s official documentation: SharePoint Framework Toolchain: Rush Stack Heft .
Do I Need to Migrate?
Short answer: Not yet, but eventually yes.
Microsoft has established a phased transition timeline:
| SPFx Version | New Projects | Existing Projects | Gulp Support |
|---|---|---|---|
| v1.22 | Default to Heft (can use --use-gulp) |
Keep using gulp | Both supported |
| v1.23 | Heft only | Keep using gulp | Both supported (critical fixes only) |
| v1.24+ | Heft only | Can use gulp but… | Officially unsupported |
Projects that upgrade their package.json to reference SPFx v1.22 or later can continue using gulp and existing customizations without breaking changes. The new toolchain only affects newly generated projects and those that choose to migrate.
So if you have an existing project with environment variables working via gulpfile.js, you can keep it running. But if you’re starting a new project, or want to benefit from Heft’s improvements, read on.
Understanding the Build Pipeline Change
If you used environment variables with gulp, your code probably looked like this:
// The gulp way - gulpfile.js (pre-SPFx 1.22)
const webpack = require('webpack');
const dotenv = require('dotenv');
build.configureWebpack.mergeConfig({
additionalConfiguration: (generatedConfig) => {
const envConfig = dotenv.parse(fs.readFileSync('./env/dev.env'));
generatedConfig.plugins.push(
new webpack.DefinePlugin({
'process.env.SPFX_API_URL': JSON.stringify(envConfig.SPFX_API_URL),
'process.env.SPFX_TENANT_ID': JSON.stringify(envConfig.SPFX_TENANT_ID)
})
);
return generatedConfig;
}
});
With Heft, the configuration moves from JavaScript-based gulp tasks to JSON configuration files:
The key differences:
| Aspect | Gulp | Heft |
|---|---|---|
| Config file | gulpfile.js | webpack-patch.json |
| Webpack hook | build.configureWebpack | Patch function |
| Task runner | Gulp | Heft |
| Orchestration | Sequential | Parallel phases |
The Four Approaches
I show here four ways to implement environment variables with Heft.
Approach 1: Pre-Build Script (Type-Safe)
Generate TypeScript constants before each build.
This approach provides full type safety and doesn’t require any webpack configuration knowledge. Choose this if type safety is your priority.
// 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
export const ENV_CONFIG = {
${spfxVars}
} as const;
`;
fs.writeFileSync('src/utils/envConfig.generated.ts', output);
Usage in your component:
import { ENV_CONFIG } from '../utils/envConfig.generated';
// Full type safety and autocomplete!
const apiUrl = ENV_CONFIG.SPFX_API_BASE_URL;
const timeout = ENV_CONFIG.SPFX_API_TIMEOUT;
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"
}
}
Approach 2: Webpack Patch (Recommended)
Use Microsoft’s official Webpack Patch Plugin.
This is my recommended approach - it’s the Microsoft-supported way to customize webpack in SPFx 1.22+ and keeps the familiar process.env.* pattern.
// config/webpack-patch.json
{
"$schema": "https://developer.microsoft.com/json-schemas/spfx-build/webpack-patch.schema.json",
"patchFiles": ["./config/webpack-patch/env-inject.js"]
}
// config/webpack-patch/env-inject.js
// IMPORTANT: Use webpack from the SPFx rig to ensure compatibility!
// Using require('webpack') directly loads a different webpack instance
// and causes "tap" errors in SPFx 1.22+
const webpack = require('@microsoft/spfx-web-build-rig/node_modules/webpack');
const dotenv = require('dotenv');
const fs = require('fs');
const path = require('path');
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`);
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;
};
Always import webpack from @microsoft/spfx-web-build-rig/node_modules/webpack in SPFx 1.22+. Using require('webpack') directly will load a different webpack instance and cause cryptic “Cannot read properties of undefined (reading ’tap’)” errors.
Usage remains the same as the old gulp approach:
const apiUrl = process.env.SPFX_API_BASE_URL;
Approach 3: dotenv-webpack (Community)
Use the popular dotenv-webpack package.
This is the industry-standard approach used in React and Node.js projects.
The dotenv-webpack package may have compatibility issues with SPFx 1.22+ Heft builds because it internally uses require('webpack'). For SPFx 1.22+, Approach 2 (DefinePlugin with proper webpack import) is more reliable.
npm install dotenv-webpack --save-dev
// config/webpack-patch/dotenv-inject.js
const Dotenv = require('dotenv-webpack');
const path = require('path');
module.exports = function(webpackConfig) {
const NODE_ENV = process.env.NODE_ENV || 'dev';
webpackConfig.plugins.push(new Dotenv({
path: path.resolve(__dirname, `../../env/${NODE_ENV}.env`),
safe: false,
systemvars: false
}));
return webpackConfig;
};
Approach 4: Heft Run Script (Advanced)
Integrate directly into Heft’s build phases.
This is the most powerful approach but also the most complex. Use it for enterprise scenarios requiring full control over the build pipeline.
// heft.json
{
"$schema": "https://developer.microsoft.com/json-schemas/heft/v0/heft.schema.json",
"extends": "./node_modules/@microsoft/spfx-heft-plugins/heft-defaults.json",
"phasesByName": {
"build": {
"tasksByName": {
"generate-env-config": {
"taskPlugin": {
"pluginPackage": "@rushstack/heft",
"pluginName": "run-script-plugin",
"options": {
"scriptPath": "./scripts/heft-env-task.js"
}
}
}
}
}
}
}
Comparison Matrix
| Approach | Type Safety | Complexity | MS Support | Best For |
|---|---|---|---|---|
| Pre-Build Script | Full | Simple | Neutral | TypeScript projects |
| Webpack Patch ⭐ | None | Medium | Official | Most projects |
| dotenv-webpack | None | Medium | Community* | React/Node familiarity |
| Heft Run Script | Full | Complex | Official | Enterprise builds |
⭐ Recommended - Microsoft’s official approach
*dotenv-webpack may have compatibility issues with SPFx 1.22+ due to webpack instance conflicts.
Full Code Repository
I have created a comprehensive sample: one unified SPFx solution containing 4 web parts, each demonstrating a different approach. This allows you to compare all methods side by side on a single SharePoint page.
View on GitHub: spfx-env-config-heft
The sample includes:
- 1 SPFx solution with 4 web parts (one per approach)
- Shared React component displaying environment variables
- Complete configuration files for all approaches
- Dev and Prod environment examples
Conclusion
Heft is the new build system for SPFx, but migration is not mandatory yet. Here’s my recommendation:
If you have existing gulp-based projects: You can keep them running without changes. Consider migrating when you have time or when starting new features.
If you’re starting a new project or choosing to migrate:
- For most projects: Use Approach 2 (Webpack Patch) - it’s Microsoft’s official approach and keeps the familiar
process.env.*pattern - For type safety: Use Approach 1 (Pre-Build Script) for full TypeScript type safety with IDE autocomplete
- For enterprise builds: Consider Approach 4 (Heft Run Script) for maximum control over the build pipeline
Heft is here to stay. Understanding these approaches will help you implement environment-specific configuration in your SPFx solutions, whether you migrate now or later.
Have questions or found an issue? Open an issue on GitHub .