Thumbnail Deploying SPFx apps with Azure DevOps pipelines - Classic variant

Deploying SPFx apps with Azure DevOps pipelines - Classic variant

There are two main reasons I really like Continuous Deployment/Integration (CI/CD) pipelines: a) I dislike repetition and b) I like releases to be predictable! And releasing an update for an SPFx app is a boring job to do twice or more, isn’t it? There are multiple systems that have CI/CD pipelines. I’m personally familiar with both GitHub and Azure DevOps. This post is about using build & release pipelines in Azure DevOps.

This blog post demonstrates how to create build & release pipelines in Azure DevOps to build and deploy an SharePoint Framework application. For this blog post I’m using the classic release pipeline because I like them and have been familiar with them for quite some time.

The following picture globally describes how the process works. I’m using a build and two release stages in this example, but it’s perfectly useful as well if you just deploy to one environment or even to a full-blown DTAP street.


We’ll use the CLI for Microsoft 365 to deploy the app. To connect to SharePoint, we’ll use an Entra application registration with a certificate. For simplicity’s sake I’ll just upgrade the app on a single SharePoint site, but it’s also possible to deploy to multiple sites.

To follow along, you’ll need some experience with Azure DevOps pipelines. But if you’ve got questions, do respond below with a comment or reach out through the socials.

Okay, so I’ve got a repository with an SPFx app that I’ve tested locally. To deploy all this we need to go through a couple of steps.

The first step is to arrange some things for authentication. When we release an SPFx app to SharePoint, we’ll need to authenticate to SharePoint to do so. We’ll use an Entra ID Application Registration and authenticate using the client credentials grant flow with a certificate. The Application will use app-only permissions on SharePoint to deploy the package. In my scenario I’m using separate Acceptance and Production environments, (Entra ID and SharePoint, everything is separate) so I’ll need two Application Registrations. One in each Entra ID Tenant.

You can do this in a lot of ways. My favorite is using scripting, so the following PowerShell code will generate a self-signed certificate for me. It will also create an Application Registration using the CLI for Microsoft 365. (You’ll need to have the proper security role for this) However: feel free to do this using the Entra ID portal. The Application Registration will have Sites.FullControl.All app-only permissions on SharePoint. This is kind of heavy, you could also use Sites.Selected and assign permissions to the App Catalog and the sites where the SPFx app should be installed. For this example I’ll keep it simple though.

# Generate a self-signed certificate and save the pfx- and cer-files
$password = ConvertTo-SecureString -String "some-safe-password" -Force -AsPlainText
$cert = New-SelfSignedCertificate -NotBefore $(Get-Date).AddDays(-1) -NotAfter $(Get-Date).AddYears(1) -FriendlyName "CI-CD-Certificate" -CertStoreLocation cert:\CurrentUser\My -Subject "CN=CICDCertificate" -KeyAlgorithm RSA -KeyLength 2048 -KeyExportPolicy Exportable
$cert | Export-Certificate -Type cer -FilePath "$PWD/CI-CD-Certificate.cer" -Force
$cert | Export-PfxCertificate -FilePath "$PWD/CI-CD-Certificate.pfx" -Password $password
$cert | Remove-Item

# Create the application registration, upload the certificate and grant it admin consent
m365 entra app add --name 'My CI/CD Application' --apisApplication '' --certificateFile ./CI-CD-Certificate.cer --certificateDisplayName 'CICD Certificate' --grantAdminConsent

📝 Be sure to write down the appId, tenantId and certificate password that you get from these steps. We’ll need these later on.

In my case I’ll run this code twice, once for each Entra ID tenant I’m signing into.

To be able to use the certificate and all the GUID’s and passwords we’ve just memorized, we’ll need to add them to Azure DevOps. We’ll navigate to our Azure DevOps project and go to Pipelines > Library. Here we’ll be able to add variables and upload the PFX-files of our certificates, so that they can be used in the Pipeline.

I generally add a variable group for each stage that I’ll create, in my case Acceptance and Production:

Library - Variable Groups

In these variable groups, I’ll create a couple of variables. Both groups will have the same variables, but with different values. The variables are:

Library - Variables

The dashboard is the site where I’m using webparts from the SharePoint Framework. The other variables should speak for themselves, they contain values we’ve written down earlier.

There’s also a secure files section. Here we’ll upload the certificates we’ve created earlier.

Library - Secure Files

After saving the values to the DevOps library, we’re finally ready to create the Build & Release Pipelines!

To create the Build pipeline, I first include the following yaml-code in an azure-pipelines.yml file in the repository of my SPFx application. This code is actually a build definition, written in yaml. As you can see it contains tasks to bundle and package the SPFx app, just like you would do yourself. It will also publish the resulting package as a build artifact, so that we can act on it in the release pipeline. As you can see I’m defining the trigger up front: this pipeline will start when code is pushed to the main branch.

    - main

  vmImage: 'ubuntu-latest'

  - task: NodeTool@0 
    displayName: 'Use Node 16.x'
      versionSpec: '16.x'

  - task: Npm@1
    displayName: 'Npm install packages'
      workingDir: '.'
  - task: Gulp@0
    displayName: 'Execute bundle gulp task'
      gulpFile: ./gulpfile.js
      targets: bundle
      arguments: '--ship'

  - task: Gulp@0
    displayName: 'Execute package-solution gulp task'
      targets: 'package-solution'
      arguments: '--ship'  

  - task: CopyFiles@2
    displayName: 'Copy sppkg-file to artifact staging directory'
      Contents: |
      TargetFolder: '$(Build.ArtifactStagingDirectory)'
      flattenFolders: true
  - task: PublishBuildArtifacts@1
    displayName: 'Publish staged artifacts'
      artifactName: 'drop' 

After committing and pushing the file to the repository, I’ll need to create the pipeline based on this pipeline definition. I can do this by going to the Pipelines > Pipelines section in my Azure DevOps project and clicking the “New Pipeline” button. I’ll select the repository and the main branch and click “Continue”. I’ll select the “Existing Azure Pipelines YAML file” option and select the azure-pipelines.yml file. I’ll click “Continue” and “Run” to start the build pipeline and run it for the first time.


This will successfully create the pipeline and run it the first time.

The Build pipeline builds the project and publishes the SPPKG as an build artifact. However, it does not deploy it to SharePoint. We could create a build pipeline that does the deployment as well, but in Azure DevOps, we typically deploy using Release pipelines. So let’s create one!

Note: It's possible to deploy the apps to SharePoint from within a Build pipeline, but I've oftentimes used a Release pipeline for that.

We’ll now create a new release pipeline in Azure DevOps. For this blog post I’m using the classic release pipelines as many of us still are familiar with. Unfortunately, we cannot create classic release pipelines using a yaml-file in the repository, like we did with the Build pipeline. We’ll need to use the Azure DevOps portal to manage these Release pipelines. So we browse to the Pipelines > Releases section of the Azure DevOps portal to get started.

We’ll click the “New Release pipeline” button. We’ll select the “Empty job” template and click “Add”. We’ll edit the default stage and call it something like “Acceptance”. We’ll add an extra stage for Production, we’ll select our Build pipeline as the artifact with a continuous deployment trigger enabled. It may look something like this, but more complicated pipelines with approvals etc are certainly possible:

Release - Overview

Note: It's also possible to create the Acceptance stage first with all its tasks and clone it. That way, you'll have less work adding tasks to each stage.

An important part to do is to link the variable groups we’ve created to the stages. This will take care that we can use the correct values in each stage. We’ll click the “Variables” tab and link the variable groups to the stages:

Release - Link variable groups

Adding the tasks to the stages is pretty straightforward.

We’ll need to set the Agent Job to run on ubuntu-latest.

We’ll need to add a couple of tasks. It should look something like this:

Release - add tasks

The npm task will need to run npm install @pnp/cli-microsoft365.

The Download secure file task will download our certificate to the agent. We’ll need to select the correct certificate and configure an ‘Output variable’. I’ve entered the value certificateFile in the ‘Reference Name’-field.

The Command Line task will contain the following code:

m365 login --authType certificate --certificateFile "$(certificateFile.secureFilePath)" --password "$(CertificatePassword)" --appId "$(AppId)" --tenant "$(TenantId)"
m365 spo set --url "$(SharePointBaseUrl)"
m365 spo app add --filePath "$(pwd)/MyRepoName/drop/my-spfx-app.sppkg" --overwrite
m365 spo app deploy --id "99238b37-8d2f-404c-bbc9-4ba62f1b89c7"
m365 spo app upgrade --id "99238b37-8d2f-404c-bbc9-4ba62f1b89c7" --siteUrl "$(DashboardUrl)"

So what does this do?

  • This script will sign in using our Entra ID app and certificate that has been downloaded to the agent.
  • It’s using the m365 spo set command to configure the SharePoint root-URL. If I wouldn’t use it, the CLI for Microsoft 365 would try to discover what my tenant is, and I’d need to grant extra permissions for it to be able to do so.
  • It will upload the app to the app catalog. The path to the SPPKG is the path to where the artifacts have been downloaded. If you don’t know for sure, you can trigger a release and check the logs to see where the artifacts are downloaded to.
  • It will deploy the app. The GUID that’s used here is the Product ID of the SPFx app. You can find this in the package-solution.json file in the sharepoint folder of your SPFx app.
  • It will upgrade the app on the site where it’s installed.

It will use all the correct variables, coming from the variable group we’ve linked earlier and the certificateFile output variable from the Download secure file task.

If we’ve configured all of this correctly, the release pipeline will automatically deploy the app after a succesful build is run. Any extra stages will be deployed after manually selecting to do so. I hope you’ve learned something from this blog post! Let me know if you have any questions!

Happy coding!

spfx cicd cli-microsoft365 azuredevops
Support me by sharing this


More blogs

Deploying SPFx apps with Azure DevOps pipelines - all YAML variant
Deploying SPFx apps with Azure DevOps pipelines - all YAML variant

In this blog post I will show you how you can deploy SPFx apps using Azure DevOps pipelines with an all YAML build & release definition.

Read more
Using the on-behalf-of flow in Azure PowerShell Functions
Using the on-behalf-of flow in Azure PowerShell Functions

A step by step guide on how to use the on-behalf-of flow in Azure PowerShell Functions.

Read more
Don't trust $PWD in Azure PowerShell Functions
Don't trust $PWD in Azure PowerShell Functions

Note to self: do not trust $PWD in Azure PowerShell Functions.

Read more


Thanks for reading

Thanks for reading my blog, I hope you got what you came for. Blogs of others have been super important during my work. This site is me returning the favor. If you read anything you do not understand because I failed to clarify it enough, please drop me a post using my socials or the contact form.

Warm regards,

Microsoft MVP | Microsoft 365 Architect

Microsoft MVP horizontal