Walkthrough: use Azure DevOps with SPFx, GitFlow and GitVersion

Walkthrough: use Azure DevOps with SPFx, GitFlow and GitVersion

Here is the scenario: as a developer, you start a new SharePoint Framework development project for a customer and this one has no processes, tools or development practices to manage the source code, deploy it automatically across multiple environments or manage versions. The purpose of this post is to provide you and end-to-end development pipeline in such a situation. Of course, this is an extreme scenario and you could likely reuse a part of the pipeline to adapt to your own context. Also, the following is very generic and not specifically tied to SPFx projects. For the recipe, I will use the following tools/technologies:

  • SharePoint Framework, as the source code to deploy
  • Azure DevOps, for the pipeline definition and automation
  • GitHub for the repository (could be anything else)
  • GitFlow, for the source code management
  • GitVersion, for version management

The base build and release definitions are available in the following git repository:


Manage your source code lifecycle efficiently: the GitFlow workflow

GitFlow is a generic branching workflow for Git (not the same as GitHub Flow). I often use this workflow for my projects because it is quite easy to understand and very convenient, even if you are the only developer on your project. This worklow can be used with any Git repository (GitHub, BitBucket, built-in AzureDevOps repository, etc) and simply define best practices about branches management. As a best practice, this does not technically constrain you so you can still do whatever you want with your branches… To help you with this workflow and if, like me, you’re not a Git command line afficionado, you can use the Git client ‘SourceTree‘ which already integrate all GitFlow actions:

When you work with GitFlow, branches typically represent a ‘stage’ in your code lifecycle and follow a specific naming convention:

  • develop: Integrates all features from developers. With GitFlow, commits whithin this branch should always be a merge commit (from a feature, release or hotfix) branch. It means that, as a developer, you shouldn’t commit directly in it (but, again, you can) and the develop branch should always be ‘stable’ (i.e. no deployment or build errors).
  • master: the latest stable version ready for production and deployable at any time. Commits in this branch should always come from release or hotfix branches
  • release/{version}: final validation before the production deployment. For instance, adjust the documentation, write the changelog, resolve minor bug fixes etc. You don’t develop any new feature here. When it is done, the update is merged to develop and master to reflect the latest version of the code.
  • feature/{feature name}: represent a new feature for you product. You can start many features in parallel if you want.
  • hotfix/{version}: used to fix a bug encountered in production that you need to fix right now. That’s why you always start an hotfix from the master branch. Like the release branch, when it is done, the update is merged to both develop and master.

GitFlow workflow (https://nvie.com/posts/a-successful-git-branching-model/)

Relationship with ‘physical’ environments

In a real world scenario, these branches can often be associated with one or more “physical” environments according to the lifecycle, for instance ‘Staging’,’Production’,’User Acceptance Testing’, etc. In a SharePoint/Office 365 world, these can be separate tenants or site collections depending of what development you make. Here are common environments I often see/use according to GitFlow branches (of course, it can be different in your context):


Associated GitFlow branch(es)

Used for



Used by developers to test a feature inside a sandbox environment isolated form other currently developed features. It is often a dedicated site collection or tenant.

Staging, QA (Quality Assurance)


Used by developers and testers to integrate all features and perform components testing (ex: UI manipulations, etc.).

UAT, Pre-production


Typically used by a small group of ‘beta tester’ end users to validate features from a functional point of view. This environment is generally identical to the production one in terms of configuration.



Final version for all end users.

Manage versions with GitFlow: GitVersion

Honestly, managing versions has always been a pain point in many of my projects. Most of the time, when you have an existing continous integration system, the version often means ‘build number’ and it is automatically set by the system and triggered from a ‘master‘ branch commit. Actually, I realized not so many developers, including me until recently, really care about this number and its meaning from a functional point of view. That’s why regarding SPFx and GitFlow, I had a lot of questions on how to improve this versioning process. For instance, when to set a new version? What format? Can I automate something regarding the version, GitFlow and deployments? How do I update these versions in my SPFx? etc.

Hopefully, I found GitVersion to help me with all these questions and, luckily, it has a default integration the GitFlow workflow (among other worfklows). With GitVersion, version numbers are not about build numbers but about commits. It makes an huge difference regarding the way you manage your versions and your release pipeline. Basically, the tool is able to look at your Git commits history and deduce versions numbers accordingly. But make no mistake, the process is only semi-automated and you still have to perform manual increments. For this part, GitVersion relies on the Semantic Versioning (SemVer) best practice. Instead of a dummy build number, the concept of semantic versioning allows you undestrand quickly the functional implications of a version. Here is the quick summary from https://semver.org.

Given a version number MAJOR.MINOR.PATCH, increment the:

  1. MAJOR version when you make incompatible API changes,
  2. MINOR version when you add functionality in a backwards-compatible manner, and
  3. PATCH version when you make backwards-compatible bug fixes.

Additional labels for pre-release and build metadata are available as extensions to the MAJOR.MINOR.PATCH format.

So when you release a new version, it is up to you to increment these numbers according the functional implications using different strategies (commit tag, branch name, commit message. etc,). GitVersion will only use these information to help you in the process of getting a meaningful SemVer version number. Here is a concrete example illustrating version increments according to the GitFlow branches and the default GitVersion configuration (‘0.1.0‘ version is the default version). The start is from bottom 😉

Pay attention to the ‘ContinousDeployment‘ and ‘ContinousDelivery, modes. These are very important to understand to be able to predict version numbers. GitVersion allows you to change these modes for each branch (among plenty of parameters) using a .yml configuration file, but honestly, for common scenarios, the default settings do the job perfectly. From a technical point of view, GitVersion is simply a CLI calculating the current version number based on your repo Git history. It means to see the current version you have to execute it from the root repo folder. It will give you the following output:

Example of GitVersion output for a specific branch

Versioning in SPFx

To make the link with SPFx, let’s see how versions work there. The versioning in an SPFx solution takes place in multiple files:

  • package-solution.json: corresponds to the version of the SharePoint .sppkg and is visible through the SharePoint App Catalog (tenant or site collection). This version number format is not SemVer compliant and follow the SharePoint Add-In standard with 4 digits.
  • package.json and Web Part *.manifest.json files: These numbers follow the SemVer standard and are mainly used for upgrade scenarios. By default, Web Parts versions follow the  package.json version using the special token ‘*‘.
This version is then available in the dataVersion property in the SPFx main Web Part class allows you to run custom upgrade logic if desired:
As a best practice, they usually all follow the same version number in the solution.

Integrate SPFx, GitFlow and GitVersion with Azure DevOps

Now we have a standardized way to manage branches and versions, we have to integrate all of this into an automated Azure Dev Ops build and release pipelines. From here, the main challenges are :

  • Handle the deployment in multiple environments according to GitFlow branches.
  • Version SPFx package according to GitVersion SemVer.
  • Handle multiple configurations per environments (ex: Web Parts configuration, app catalog URL, environment credentials, etc.).
  • Get an unique and reusable build and release definition for all environments.

Build pipeline configuration

1. Configure repository connection

In my case, I use a GitHub repository. Nothing special here, except you may have to generate personal token with appropriate permissions to connect to your repository (especially for webhooks).

2. Configure triggers

The second thing to do is to configure the triggers for the build according to GitFlow. The following configuration means the build will start if a commit is made on these branch:

You may notice the ‘feature/*’ branches are not here. In my opinon, this is not necessary since when developers work on feature branches, they often use their own environment (tenant or site collection with dedicated app actalog) and likely deploy ‘manually’ their SPFx packages for a time saving purpose. At least, this is my case.

3. Configure build name

I configure the build name to reflect the current version generated by GitVersion (this number will be determined later in a specific build task). I also add the build id to get an unique build name.

4. Configure build tasks

The complete sequence looks like this. I mainly reused build definition from the sp-dev-build-extensions SharePoint repository thanks to @baywet.

Build pipeline tasks



Use Node 8.x

Prerequisite for SPFx


Available through the marketplace, this task gets the current version according to the triggered branch. It does the same as if you run the GitVersion cmd on your local repo for the targeted branch before merging/pushing. All version related variables are then available in the other tasks using $(GitVersion.<variable_name>) (typically the variables from the console output above). If you don’t manually set a build name, this one will be automatically set to the GitVersion output version.

npm install

Install all npm packages from package.json.

gulp update-properties

Updates Web Part manifest configuration according to current branch. In certain scenarios, you may have different configurations for you Web Part properties according to the targeted environment. For instance, I’m thinking about back-end service URL (like Azure Functions, Flows, etc.) pointing to different environments as well. Because GitFlow branches are associated with a physical environment, I use a dedicated gulp task to choose the right configuration according to the branch.

In addition, I use the corresponding structure for configuration files per environment for each Web Parts. It is one solution among others to deal with multiple configurations.

gulp build –ship

Build sources (TypeScript/Sass) for SPFx.

gulp bundle –ship

Bundle SPFx sources (Webpack)

npm version

Update the version determined by GitVersion in the package.json file. You can use $(GitVersion.SemVer) or $(GitVersion.MajorMinorPatch) variable here. Notice the ‘–no-git-tag-version‘ flag to avoid tagging branches (because tags are handled by GitFlow).

gulp update-version

Update the SPFx version in the package-solution.json file using a specific gulp task. Because SPFx package solutions rely on the old SharePoint Add-In model (4 digits format), the version is not SemVer compliant so I can’t include pre-release tags from GitVersion here. Anyway, in my case and for convenience, I use the MajorMinorPatch version number + the unique build id to identify precisely the current deployed build for an environment just by looking the SPFx app package version in the site contents. You could leave the last digit to ‘.0’ or choose something else.

gulp update-version task

Generated build number

Allows you to identify this build uniquely

The gulp task is inspired by the one from Stefan Bauer blog post.

gulp package-solution –ship

Generate the SPFx .sppkg package.

Copy Files

Just copy the generated package to the default ‘drop‘ artifact location. This location will be used by the release pipeline to get the package.

Publish artifact

Publish the artifcat to be consumed by the release pipeline.

Release pipeline configuration

The release pipeline will be used to deploy the SPFx package into different environments according to the GitFlow code lifecycle. For instance, when a commit will be made on ‘develop’, the package will be deployed automatically to the ‘Staging’ environment. For ‘master’, it will be the ‘Production’ environment and so on… The complete release pipeline looks like this. It does not cover gates, approvals etc. that you can add afterwards.


Azure Dev Ops GitFlow release pipeline

Each stage represents a physical environment (here it will be different SharePoint site collections within the same tenant).

1. Setup the release name

To match the release with the build, I use the build number for the release name:

2. Configure artifact

The artifcat comes directly from the build pipeline and ‘starts’ a new release. The configuration and triggers look like this:

As you can see and like the build pipeline, the triggers correspond to the GitFlow branches. They will be used to determine the right stage (i.e. environment).

3. Configure stages for environments

To deploy on the right environment, I use ‘pre-deployment‘ conditions filtering on the triggered Git branch from the artifact. Here are the settings for the all 3 environments:

Staging  = develop

Pre Production = release/*,hotfix/*

Production = master


Release pipeline tasks

The tasks are the same for all stages. For example, ‘Pre-Production‘, ‘Production‘ are just clones from ‘Staging‘ and only differ about their pre-deployment conditions. This way you could add other environments pretty easily. Again I mainly reused release definition from the sp-dev-build-extensions SharePoint repository.



Use Node 8.x

Prerequisite for Office 365 CLI

npm install office 365 cli

Install the Office 365 cli package

spo login

Login to the SharePoint environment (i.e. the site collection).

spo app add (site collection)

Add the solution to the site collection app catalog (need to be activated before).

spo deploy app (site collection)

Deploy the package to the site collection.

As you can see in the tasks, I use variables for URLs and credentials. To be able to distinguish multiple deployment URLs (i.e. site collections in this case), I use release piepline variables and the ‘scope‘ attribute. It means the variable will be only available in corresponding stage so you can set the same name for all stages:

Test the pipeline

To test the whole pipeline, simply start pushing your commits in configured branches according to the GitFlow workflow and you will see builds and releases triggered automatically and your package deployed to the right environment:

Triggered builds according to GitFlow

Triggered releases


DevOps is a indeed very large topic and I’m definetely not an expert. However, for my day-to-day SPFx requirements, coupled with GitFlow, this Azure DevOps pipeline does the job in the case there is nothing in place for my new projects. I would happy to hear your thoughts about how you manage your versions and deployments with SPFx. Also free to improve this pipeline 😉

Useful links


Add yours
  1. Ben

    Am still getting ridiculously named assemblies like “0.1.0-beta-1+74.Branch.release/RC1.Sha.17380921n2n9289rjh2908ru908rjn2oiun98o”

    I have no idea how to configure this junk correctly. I just want something like 2.0.0-RC1-2 or something more sane.

    • Franck Cornu

      A bit late but what GitVersion variable are you using? You can check all available variables by running GitVersion on your local project. The one you describe seems to be ‘Informationalversion’.

  2. Michaël Maillot

    Salut, merci beaucoup pour cet article très complet !

    Je suis en train de mettre en place du CI/CD pour un projet et je bloque sur la tâche de build pour la montée de version (“update-version”). En effet, dans les traces de log je n’ai rien qui apparaît. Pourtant en local, la commande s’exécute bien. J’ai bien pointé le build sur la bonne branche qui contient le gulp file à jour, la variable GitVersion est bien prise en compte puisqu’elle apparaît dans la console d’exécution de manière interprétée.

    Aurais-tu une idée sur ce pb ? Merci 🙂

    • Franck Cornu

      Salut! Un peu tard pour la réponse désolé. As-tu vérifié que la tâche gulp est bien configurée correctement dans DevOps (–version) et que la variable GitVersion que tu utilises contient bien une valeur. Aussi, qu’est ce que ça donne avec une autre variable GitVersion? Tu peux toujours modifier la tâche pour y inclure plus de log et voir si elle est appelée ou non.

  3. Mark van Dijk

    Bonjour Franck.
    Thanks for a very interesting article! I think it brilliantly ties together the knots of Azure DevOps, SPFx, GitFlow and GitVersion into one ‘flow’.
    I’m trying to implement this with one of my customers but one thing I don’t really get: I understand that GitVersion ‘guesses’ the version number automatically and you’ve added build tasks to update the package.json and package-solution.json. How are you pushing these changes back to your Git repository however? I’m trying out the sequence of tasks as you describe them, but the version never gets updated in the repo. Am I missing something?

    • Franck Cornu

      Hi Mark. Actually, you don’t need to push back the version into your repository. It is all about tags. If you want to know the version, just follow the tags history on the corresponding branch, you don’t need to look at the code. Version is important for releases. If you really want to add it to your code, you can add a git task to manually commit the change from AzureDevOps I guess.

  4. Michaël Maillot

    Hello, cette fois c’est moi qui te répond un peu tard. En fait, j’avais trouvé d’où provenait le problème : cela provenait de l’agent utilisé, qui ne savait pas interpréter la commande “update-version” et qui l’évitait. Quant à savoir pourquoi ce comportement… Pour info, j’avais ajouté l’info à un ticket existant (mais déjà clôturé) : https://github.com/microsoft/azure-pipelines-tasks/issues/6048#issuecomment-498018789

    Maintenant, je me bats avec la configuration de GitVersion et l’incrément qui diffère entre les plateformes, une toute autre histoire 😀

    Merci en tous cas pour ton article !

  5. Brady Kelley

    This is very helpful. I am running into a problem with this implementation that may be due to an update with Azure DevOps. I cannot seem to specify a wildcard value for the Artifact Filter (ex. ‘release/*’) for the stage trigger configuration. It will only allow me to select a specific branch, not type in a value with a wildcard. I can use a wildcard value in the build filter just fine, just not the stage artifact filter. This makes it difficult to set up a similar pipeline as you have here. I am not sure how to workaround this. Any thoughts?

    • Franck Cornu

      Hi Brady, you mean for the release pipeline? You should be able to do this. I use this in my current setup. You need to enter the value as free text ‘release/*’ if your branch if not listed, then save.

  6. Kristian Mosti

    Hi Brady, I just set this up (although for on-prem 2016, so not able to include the final app catalog steps due to lack of ALM), and this is the trigger in my pipelines.yml:

    batch: true
    – master
    – release/*
    – features/*
    – bugfix/*
    – README.md

    Works well on my end, using the default ubuntu vm image.

    @Franck: Merci beaucoup pour ton éxplication complet sur ce sujet, tu m’as bien sauvé une bonne semaine de boulot 🙂

+ Leave a Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.