How to Make Custom Project Stage Tracker in PPM

All Stage Done

I often get requests from clients that they would like a custom Project Stage tracker in PPM so I’ve finally decided to put together instructions on how to create a simple project stage tracker.

The default Stage tracker, like the one below, gets the job done however multiple branches it can be a little confusing.  Also, I find this to be an easy way to add a little personalization to your environment.  

So if your company has a complex project life cycle or one with multiple branches like the image below, you may require something different from the default stage tracker to better depict the process.

From Create Stage to Closure Stage,

Stage Flow
Stage Flow


Step 1. Be sure to have an image of what your tracker looks like, make 1 for every stage and color it differently to identify each current stage.  Basically we are trying to make it look like a progressive image.

Step 2. In your SharePoint’s site collection where the PWA is installed, go to Site Contents. Make a Document Library for your tracker images and upload it.

Step 3. In your Document Library where you uploaded your images, add a new custom meta data called ‘StageGUID’

Step 4. Now, we need to get each PWA Stages GUID so we can put it the the ‘StageGUID’ metadata in your document library and complete our image reference. In your page, click the gear located in the top right corner then click ‘PWA Settings‘.

You will be redirected to PWA settings page, locate the ‘Workflow and Project Detail Pages’ section and click ‘Workflow Stages

You will be redirected to “Workflow Stages” page.

Now, to get the guid of the stage, click any of the stages in the table. You will be redirected to the details of the stage you clicked. Locate the ‘System Identification Data’ section at the bottom part of the page

Copy the stage guid and go to your document library you created earlier and paste it in the meta data of the image you want to be associated with the stage.

Step 5. Go to your PWA projects page, click any project you made that has an enterprise project type (EPT) associated with it. Any project will be fine as long as you are in the WorkflowStageStatus.aspx page. Click the gear in the top right corner of the page and click ‘Edit Page

Step 6. It is expected that you can see a lot of ‘WebPart Zones‘ in the page, some were used by SharePoint out of the box functionality. Click any of the web part zone and add a ‘Script Editor‘ Web Part. Edit the Script Editor web part and add jquery script (there are a lot of jquery source, you can download it or you can just reference to any cdn available in the internet i got mine in

<script src=""   

Step 7. Add another Script Editor to where you will put your Custom Workflow Stage Status Tracker. Edit it and insert this code snippet.

<div id="stageImageContainer"> 
    <img id="stageImage"/> 
    <div id="ErrorMsg" style="display: none;">

Step 8. Make another ‘Script Editor‘ to insert the additional scripts that will do the graphics call depending on what stage you associate with the image in your image document library.

<script type="text/javascript">
       var projid = _getQueryStringParams("ProjUid"); 
       if(projid){ var imageListName = "Stage Images"; 
           getProjectStagesById(getSiteUrl(), projid, function(projectStages){ 
             var currStageId = projectStages.Id; // get document library getListItemsByFilter(getSiteUrl(), imageListName, "select=StageUid,StageName,EncodedAbsUrl&$filter=StageUid eq '"+currStageId+"'", function(imageAssets){ 
             if(imageAssets.length > 0){ 
               $("#stageImage").attr("src", imageAssets[0].EncodedAbsUrl); 
               $("#ErrorMsg").html("<h3>Image for Stage Not Found</h3>").removeAttr("style"); 
             } }); 
            }); // end rest get project stages 
            console.log("Missing Project UID Query String"); 
  }); // end document ready

It looks like this if you paste it in your text editor. ( i used sublime as my text editor)

1.  It is by default / out of the box ability to have a query string ‘ProjUid‘ of project’s workflow stage status page to let the page identify which project it will display.

2. The string “Stage Images” is the list title that i used for the stage images document library. In the script, change that string to whatever title you used to make your document library.

3. Is the key scripts to get the right images to show in your custom tracker. It will get the stage that has the same stage id you put in the “StageGUID” metadata in your document library.

This is the full code with the utilities and additional stuffs.

<script type="text/javascript"> 
var projid = _getQueryStringParams("ProjUid"); 
 if(projid){ var imageListName = "Stage Images"; 
    getProjectStagesById(getSiteUrl(), projid, 
             var currStageId = projectStages.Id; // get document library 
    getListItemsByFilter(getSiteUrl(), imageListName, "select=StageUid,StageName,EncodedAbsUrl&$filter=StageUid eq '"+currStageId+"'", 
          if(imageAssets.length > 0){ 
               $("#stageImage").attr("src", imageAssets[0].EncodedAbsUrl); 
          }else{ $("#ErrorMsg").html("<h3>Image for Stage Not Found</h3>").removeAttr("style"); } 
}); // end rest get project stages }else{ console.log("Missing Project UID Query String"); } }); // end document ready

// _______________________________________________________________________________________UTILITIES//get url query stringsfunction _getQueryStringParams(sParam){ var sPageURL =;  // console.log("sPageURL : " + sPageURL ); if(sPageURL){ var sURLVariables = sPageURL.split('&');  for (var i = 0; i < sURLVariables.length; i++){  var sParameterName = sURLVariables[i].split('=');  if (sParameterName[0] == sParam){  return sParameterName[1];  }  } }else{ // console.log("no query string params"); } return null; //query string not found}
function getProjectStagesById(targetUrl, projectId, callback) {
 var restUrl = "/_api/ProjectServer/Projects(guid'"+projectId+"')/Stage";    
   url: targetUrl + restUrl,        
   method: "GET",        
   headers: { "Accept": "application/json; odata=verbose" },
   success: function (data) { (data.d); },        
   error: function (error) { callback(JSON.stringify(error)); }    

// retrieve item based on query passed in function call parameter: NOTE: 'query' parameter is optional, leaving it null will be treated select=*function 
getListItemsByFilter(targetUrl, listName, query, callback){ 
   var restUrl = "/_api/web/lists/getbytitle('"+listName+"')/items";    
     url: targetUrl + restUrl+"?$"+query,
     method: "GET", 
     headers: { "Accept": "application/json; odata=verbose" },
     success: function (data) { callback(data.d.results); },
     error: function (error) { callback(JSON.stringify(error)); }    

// get current urlfunction getSiteUrl(){ return _spPageContextInfo.webAbsoluteUrl;}</script>

Step 9. Hide the OOTB Workflow Stage Status. Edit the Status Web Part and uncheck the “Workflow Visualization”


Then you’re done. You have just made your custom Stage status tracker! Thanks for reading. Hope it helps!

If you need help with this solution or would like to learn how we have evolved this to include a hover over panel to show what tasks are due in each stage, please stay tuned for a follow up to this post or email me

Exporting and Importing Reusable OOTB Content Editor Web Part

By using web parts, you can modify the content, appearance, and behavior of pages of a SharePoint site by using a browser. Web parts are server-side controls that run inside a web part page: they’re the building blocks of pages that appear on a SharePoint site.

There are lot of ways on how to create a webpart, in this tips, we will be using a simple Out of the box (OOTB) Content Editor webpart to generate a reusable webpart.

First, in sharepoint site, we need to create a page to house our webpart, any page type will do, it could be a wiki or a webpart page.

In my case, i have created a webpart page

Click the ‘Add a Web Part’ zone to add a Web Part. When the ribbon option shows up, under the ‘Categories’ click the ‘Media and Content‘ then on the right side option, select ‘Content Editor‘ then click ‘Add’ in the bottom right part of Web Part options.

After our content editor is placed in the page, we now need to have a text file where we will put our reusable content.

We will create a text file name it with ‘jumbotrons‘ with this sample content i grabbed from bootstrap tutorial.

<div class="">
 <div class="jumbotron">
 <h1>Bootstrap Tutorial</h1> 
    <p>Bootstrap is the most popular HTML, CSS, and 
      JS framework for developing responsive, 
      mobile-first projects on the web.

Upload it in your ‘Site Assets’ list.

Back in your sample page, on the top right of the Content Editor web part, click the tiny drop down arrow and click ‘Edit Webpart

Go to “Site Assets” and copy the Url of the ‘jumbotrons.txt’

In the Content Editor Option, put the link of the jumbotrons.txt you copied then click ‘OK‘ at the bottom of the options box

Now, we have this webpart with no bootstrap css applied to it

We need to insert a script editor anywhere in the page and add some references to apply the bootstrap css. Adding a script editor step is like adding a content editor, just follow the steps above on how to add the content editor webpart.

In the ‘Script Editor‘ Web Part , insert the CDN references then click ‘Insert

<link rel="stylesheet" href="">
<script src=""></script>
<script src=""></script>

So now, our content editor is ready to be exported, in doing so, click the ‘Export‘ button in the Web Part pop-up option

It will download a ‘.dwp‘ after. Go to the page where you will import the Web Part then click any on the Web Part zones.

After the Web Part options show up, click the ‘Upload a Web Part‘ under the ‘Categories’ pane. Select the downloaded .dwp file then click ‘Upload‘.

After you uploaded your Web Part , you can now see it and can be inserted anywhere in different pages

So this is the page looks like inserted with our exported and imported Content editor Web Part.

Though the content in the page was kinda repeating, I just want to show you the concept on how to make a simple reusable Out of the box content editor Web Part so when ever you’ll need to insert the same content in many pages, you can add it easily with just 2 clicks. Edit the page, add the Web Part then your’e done.


How To Create Project Site Using Sharepoint REST API

How REST requests differ by environment Building and sending an HTTP request may vary according to language, library, and add-in type, so you often need to change one or more request components when you’re translating a request from one environment to another. For example, jQuery AJAX requests use data and type parameters to specify the request body and type, but cross-domain library requests use body and method parameters to specify those values. The following sections describe other common differences across environments.

The way you get and send the form digest value depends on the add-in When you send a POST request, the request must include the form digest value in the X-RequestDigest header. However, the way you get and send the value differs by add-in:

  • In SharePoint-hosted add-ins, you can just pass the following header: “X-RequestDigest”: $(“#__REQUESTDIGEST”).val()
  • In cloud-hosted add-ins that use OAuth, first retrieve the form digest value by sending a request to the contextinfo endpoint, and then add it to requests, as shown in Writing data by using the REST interface.
  • In cloud-hosted add-ins that use the JavaScript cross-domain library, you don’t need to specify the form digest value. By default, SP.RequestExecutor automatically handles this for you. (It also handles the content-length value.)

Add-ins that use OAuth must pass access tokens in requests

Cloud-hosted add-ins use either OAuth or the cross-domain library to authorize access to SharePoint data. Add-in components with code that runs on a remote web server must use OAuth to authorize access to SharePoint data. In this case, you need to include an Authorization header to send the access token. See Reading data with the SharePoint 2013 REST interface for an example that adds an authorization header to an HTTPWebRequest object.

Code snippet here:

url: "http://<site url>/_api/web/webinfos/add", 
type: "POST", 
data: JSON.stringify( 
  { '__metadata':  
     {'type': 'SP.WebInfoCreationInformation' },
     'Url': 'RestSubWeb', 'Title': 'RestSubWeb', 
     'Description': 'REST created web', 'Language':1033, 
     'WebTemplate':'sts', 'UseUniquePermissions':false} } ), 
headers: { 
  "accept": "application/json; odata=verbose", 
  "content-length": <length of post body>, 
  "X-RequestDigest": $("#__REQUESTDIGEST").val() }, 
success: doSuccess, error: doError 

MSDN Resource Link: