SharePoint Apps for Public Site: Workflow approach

Apr 21
SharePoint Apps for Public Site: Workflow approach

In my previous post I've described several approaches available to developers who builds apps for anonymous users in SharePoint 2013 and in this post I'd like to describe in details how to make it using a Workflow.

Approach overview

  • Create a new App and specify the necessary permissions for your logic
  • Add a site workflow
  • Make an infinite loop
  • Make a call to the REST API to get data in JSON format
  • Save results inside a library in the App Web again using REST API
  • Use delay activity to postpone execution to (1, 5, 30... minutes)
  • Make an AJAX call to get the saved file
  • Display data for user as required by App logic

WF logic

WF overview


Get data

First of all, we need to build a request url combining current host web (app web depending on your request) with a relative path ("_api/web/lists" in my case). Then we need to create a dictionary for request headers with one value (Accept = "application/json; odata=verbose") to get data in JSON instead of XML. Then I pass these variables to HttpSend activity as Uri and RequestHeaders properties, select Get as a Method and map RequestContent to a new variable (data) for later usage. Also I log ResponseStatusCode in Workflow History.

Save to library

The save logic is almost the same as get with a few changes: now the request relative path is "_api/web/getfolderbyserverrelativeurl('Data')/files/add(overwrite=true,url='data.js')", HttpSend activity method is POST and RequestContent reuses the data variable from above "var appData = " + data.ToString() + ";" .

Implementing Timer Job using Workflow 2013

As shown in the 1st workflow picture I'm using an infinite DoWhile activity (with always true end condition), the last activity in the Body area is Delay activity that is configured for 1 minute duration of delay.

WF start logic

For SharePoint 2013 style workflow there is a new API that can be used from a JS code using CSOM. For regular App users I've created a button that checks whether an instance of the workflow is started and (if not) starts a new instance. I had to create 2 functions due to async request execution. It's required to get a list of App Web workflow instances to check whether my workflow is already running and to get a subscription by id to start a new workflow instance. And it's possible to get these 2 things in one request using CSOM.

function StartWorkflow() {
    log("Adding site workflow.");

    context = SP.ClientContext.get_current();
    web = context.get_web();

    wfManager = new SP.WorkflowServices.WorkflowServicesManager.newObject(context, web);

    subscription = wfManager.getWorkflowSubscriptionService().getSubscription(subscriptionId);

    instances = wfManager.getWorkflowInstanceService().enumerateInstancesForSite();

    context.executeQueryAsync(initWorkflowSubScriptionService, err);

function initWorkflowSubScriptionService() {
    log("WF manager is received.");
    log("Workflow Subscription is received.");

    var isWorkflowRunning = false;

    if (instances.get_count() > 0) {
        for (var i = 0; i < instances.get_count() ; i++) {
            var workflowInstance = instances.getItemAtIndex(i);
            if (workflowInstance.get_workflowSubscriptionId().toString() === subscriptionId && workflowInstance.get_status() === 1) {
                isWorkflowRunning = true;

    if (isWorkflowRunning) {
        log("Workflow is already running.");
    else {
        wfManager.getWorkflowInstanceService().startWorkflow(subscription, new Object());

            function (sender, args) {
                log("Workflow started successfully.");

Get data using jQuery getScript function

jQuery allow dynamic loading of a script file. By default, it has caching disabled thus I'm always getting actual data, but caching can be enabled in the getScript options to improve performance if your data is suitable for caching.

The script file has a very simple format:

var appData = null;
var appData = {...};

and can be used as a JS variable after the script is loaded.

$.getScript("../data/data.js", function (data, textStatus, jqxhr) {
    if (appData) {
        log("Lists: ");
        for (var i = 0; i < appData.d.results.length; i++) {
            var r = appData.d.results[i];
            if (!r.Hidden) {
    } else {
        log("No data received.");

The result

Live demo available here.



You can download the App and its code on

Web Part Error: Activation of solutions with sandboxed code has been disabled. Correlation ID: 2521559e-50e4-5000-40ca-eccec6b19e83.