The SharePoint Framework (SPFx) v1.22 introduces the Heft-based build toolchain, replacing the previous Gulp-based system. One common customization developers added to SPFx projects was linting stylesheets using the popular gulp-stylelint task. With the new toolchain, you can achieve the same result, and more, by creating a custom Heft plugin.
In this article, you’ll learn how to add stylesheet linting to your SPFx projects using a custom Heft plugin in one of two ways and I’ll walk you through creating your own Heft plugin from scratch. I’ll then show you how to use my @voitanos/heft-stylelint-plugin package.
If you’re new to the Heft-based toolchain, I recommend starting with my article on understanding how the Heft toolchain works before diving in here: SharePoint Framework v1.22: What’s in the Latest SPFx Update
Understanding your options
When writing Microsoft’s documentation for the SPFx v1.22 heft-based toolchain, I included a page on customizing the build with the Heft Run Script plugin. That approach uses Heft’s built-in Run Script Plugin to execute arbitrary code during the build process. It works, but there’s a more elegant and reusable solution: creating a custom Heft plugin.
A custom Heft plugin gives you several advantages over the Run Script approach. You get better integration with Heft’s plugin architecture, cleaner configuration, and the ability to package your plugin for reuse across multiple projects. This last point is particularly valuable if you’re working on multiple SPFx projects or want to share your customizations with your team.
You have two options when creating a custom Heft plugin:
- Include the plugin in your SPFx project: This approach keeps everything in one place but limits reusability. The plugin only works within that specific project.
- Create a separate npm package: This approach requires more initial setup but makes your plugin installable in any SPFx project. It’s the better choice for plugins you’ll use across multiple projects.
Creating your own Heft plugin
Let’s walk through creating a custom Heft plugin from scratch. This technique will apply to any custom build task you want to add, not just Stylelint, but I’m going to stick with the Stylelint scenario I wrote about in the SPFx documentation, and covered in my previous article ( Customize SPFx Heft Toolchain: Plugins, Scripts, and Webpack) so you have a good comparison of your options:
Option 1: Plugin in the same project
If you only need the plugin for a single project, you can include it directly in your SPFx project. This approach is simpler to set up but doesn’t scale well if you need the same functionality across multiple projects.
Start by installing Stylelint and a popular community configuration:
npm install stylelint stylelint-config-standard-scss --save-dev
Next, create a configuration file ./.stylelintrc in your project root:
{
"extends": "stylelint-config-standard",
"plugins": ["stylelint-scss"],
"rules": {
"at-rule-no-unknown": null,
"scss/at-rule-no-unknown": true,
"value-list-comma-space-after": "always-single-line",
"declaration-empty-line-before": "never"
}
}
Next, create a new file ./build/StylelintPlugin.ts, in your project with the following code. This file implements the IHeftTaskPlugin interface which requires a single apply() method. Heft will execute this method when it runs the plugin. The file also includes a reference to the options the plugin supports.
import * as path from 'path';
import { readFileSync } from 'fs';
import type {
HeftConfiguration,
IHeftTaskSession,
IHeftTaskPlugin,
IHeftTaskRunHookOptions,
IScopedLogger
} from '@rushstack/heft';
import stylelint from 'stylelint';
export const PLUGIN_NAME: string = 'stylelint-plugin';
export interface IStylelintPluginOptions { }
export default class StylelintPlugin implements IHeftTaskPlugin<IStylelintPluginOptions> {
public apply(taskSession: IHeftTaskSession, heftConfiguration: HeftConfiguration, options: IStylelintPluginOptions): void {
taskSession.hooks.run.tap({
name: PLUGIN_NAME,
stage: Number.MIN_SAFE_INTEGER
}, async () => {
// output version of stylelint
const stylelintPkgPath = path.join(heftConfiguration.buildFolderPath, 'node_modules/stylelint/package.json');
const stylelintPkg = JSON.parse(readFileSync(stylelintPkgPath, 'utf-8'));
taskSession.logger.terminal.writeLine(`Using Stylelint version ${stylelintPkg.version}`)
// run stylelint on SCSS & CSS files using Node.js API
const result = await stylelint.lint({
files: 'src/**/*.scss',
configFile: path.join(heftConfiguration.buildFolderPath, '.stylelintrc'),
cwd: heftConfiguration.buildFolderPath
});
// process results
if (result.errored || result.results.some(r => r.warnings.length > 0)) {
for (const fileResult of result.results) {
if (fileResult.warnings.length > 0) {
for (const warning of fileResult.warnings) {
const relativePath = path.relative(heftConfiguration.buildFolderPath, fileResult.source);
const formattedWarning = `${relativePath}:${warning.line}:${warning.column} - (${warning.rule}) ${warning.text}`;
taskSession.logger.terminal.writeWarningLine(formattedWarning);
}
}
}
}
});
}
}
Next, create a schema file, ./build/heft-stylelint-schema.json, describing the configuration options the plugin supports. In this scenario, there are no settings, but you should still have one:
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "Heft Stylelint Plugin Options Configuration",
"description": "This schema describes the \"options\" field that can be specified in heft.json when loading \"@heft-stylelint-plugin\".",
"type": "object",
"additionalProperties": false,
"properties": { }
}
Finally, the last step is to tell Heft where this plugin is by adding a file ./heft-plugin.json in the root of the project. As you can see from the file’s contents, this gives the plugin a name, the location of the main code file, the schema for it’s configuration, and any parameters it will accept:
{
"$schema": "https://developer.microsoft.com/json-schemas/heft/v0/heft-plugin.schema.json",
"taskPlugins": [
{
"pluginName": "stylelint-plugin",
"entryPoint": "./build/StylelintPlugin",
"optionsSchema": "./build/heft-stylelint-schema.json",
"parameters": []
}
]
}
Using the plugin is no different than how I demonstrated in
my previous article. Within the project’s ./config/heft.json, just set the pluginPackage to the name of the current project and the pluginName to the name you assigned the plugin:
{
"$schema": "https://developer.microsoft.com/json-schemas/heft/v0/heft.schema.json",
"extends": "@microsoft/spfx-web-build-rig/profiles/default/config/heft.json",
"phasesByName": {
"build": {
"tasksByName": {
"stylelint-plugin": {
"taskDependencies": [ "sass" ],
"taskPlugin": {
"pluginPackage": "spfx-heft-stylelint-plugin",
"pluginName": "stylelint-plugin"
}
}
}
}
}
}
Option 2: Plugin as a separate npm package
For better reusability, create your plugin as a standalone npm package. This requires more initial setup but pays off when you need to use the same plugin across multiple projects.
In this scenario, create a new Node project just like you would for any other type of npm package. You need all the same files as I outlined in the first option above.
This is how I created the @voitanos/heft-styletlint-plugin package. The repo that contains this package, https://github.com/voitanos/heft-plugins, is actually a monorepo that will contain multiple packages. That repo is configured to publish each plugin as a separate npm package.
It isn’t required to have each plugin be in a separate npm package… you can group multiple plugins together. I went with this option so installing one wouldn’t included dependencies for other plugins you may not want to use in your project.
The core components
Regardless of which approach you choose, every custom Heft plugin needs these core components:
- Dependencies: Install the Heft SDK and any libraries your plugin needs. For a Stylelint plugin, you’ll need
@rushstack/heft,stylelint, and the Stylelint configuration packages. - Plugin code: Create a TypeScript file (such as StylelintPlugin.ts) that implements the Heft plugin interface. This file contains the logic that runs during the build process.
- Plugin manifest: Create a heft-plugin.json file that describes your plugin to Heft. This file defines the plugin’s name, entry point, and any configuration options it accepts.
Using the @voitanos/heft-stylelint-plugin
I’ve created a Heft plugin that adds Stylelint support to SPFx projects: @voitanos/heft-stylelint-plugin. This plugin is currently in beta, with full documentation coming soon. Here’s how to add it to your project.
The @voitanos/heft-stylelint-plugin is the first in a collection of Heft plugins I’m building for SPFx developers. Subscribe to my newsletter to learn about new plugins as they’re released.
Install the plugin
Start by installing the plugin as a dev dependency in your SPFx project:
npm install @voitanos/heft-stylelint-plugin --save-dev
Add the Stylelint configuration
Create a .stylelintrc file in the root of your project. This file tells Stylelint which rules to apply when linting your stylesheets:
{
"extends": "stylelint-config-standard-scss",
"rules": {
"at-rule-no-unknown": null,
"scss/at-rule-no-unknown": true,
"value-list-comma-space-after": "always-single-line",
"declaration-empty-line-before": "never"
}
}
This configuration extends the popular stylelint-config-standard-scss preset and adds a few rules that work well with SPFx projects. Feel free to customize these rules based on your team’s coding standards.
Configure the Heft build toolchain
Next, you need to tell Heft to use the plugin during the build process. If your project doesn’t already have a ./config/heft.json file, create one. Then add or update the configuration to include the Stylelint plugin:
{
"$schema": "https://developer.microsoft.com/json-schemas/heft/v0/heft.schema.json",
"extends": "@microsoft/spfx-web-build-rig/profiles/default/config/heft.json",
"phasesByName": {
"build": {
"tasksByName": {
"stylelint-plugin": {
"taskDependencies": [ "sass" ],
"taskPlugin": {
"pluginPackage": "@voitanos/heft-stylelint-plugin"
}
}
}
}
}
}
The taskDependencies array ensures the Stylelint task runs after the sass task completes. This is important because you want to lint the SCSS files after Sass has processed them. In this case, I don’t need to set the the taskPlugin.pluginName property because it’s the only plugin in the package.
Run the build
With the plugin installed and configured, run the build command to see Stylelint in action:
heft build
You’ll see Stylelint output in the build log, reporting any stylesheet issues it finds. The plugin integrates with Heft’s logging system, so warnings and errors appear with the same formatting as other build tasks.

Running the stylelint as part of the **build** phase, writing it’s findings to the console
Sample projects
I’ve prepared two sample projects that demonstrate both approaches to creating a custom Heft plugin. Fill out the form below to get these sample projects and explore working implementations of:
- In-project plugin: An SPFx project with the Stylelint plugin included directly in the project
- npm package plugin: An SPFx project that uses the plugin installed from an npm package
These examples give you working code to reference as you build your own custom Heft plugins.
Learn more about the SPFx toolchain
The Heft-based toolchain opens up powerful customization options for SPFx developers. Understanding how to create custom plugins is just one piece of the puzzle.
I’m running a hands-on SPFx workshop in January that covers the build toolchain in depth. You’ll learn not just the “how” but the “why” behind these tools—the kind of understanding that helps you solve problems on your own.
Learn more and enroll in the workshop: Learn SharePoint Framework Development - January 13-22, 2026.
I’m updating my Mastering the SharePoint Framework course with comprehensive coverage of the new toolchain. Stay tuned for those updates!
Wrapping up
Custom Heft plugins give you a clean, reusable way to extend the SPFx build toolchain. Whether you’re adding Stylelint, custom file processing, or any other build task, the plugin architecture keeps your customizations organized and maintainable.
The @voitanos/heft-stylelint-plugin is the first in a collection of plugins I’m building for SPFx developers. More are on the way—subscribe to my newsletter so you don’t miss them.
What customizations are you adding to your SPFx build toolchain? Have questions about creating your own Heft plugins? Leave a comment below!
Download Article Resources
Want the resources for this article? Enter your email and we'll send you the download link.
Microsoft MVP, Full-Stack Developer & Chief Course Artisan - Voitanos LLC.
Andrew Connell is a full stack developer who focuses on Microsoft Azure & Microsoft 365. He’s a 21-year recipient of Microsoft’s MVP award and has helped thousands of developers through the various courses he’s authored & taught. Whether it’s an introduction to the entire ecosystem, or a deep dive into a specific software, his resources, tools, and support help web developers become experts in the Microsoft 365 ecosystem, so they can become irreplaceable in their organization.





