When building enterprise solutions in SharePoint Framework, long-running processes are common:

triggering Power Automate flows

importing files

processing lists

migrating content

generating reports

synchronizing data


Building Real Progress Tracking in SPFx with PnP Progress, PnPjs, and Power Automate

When building enterprise solutions in SharePoint Framework, long-running processes are common:

  • importing files
  • processing lists
  • migrating content
  • generating reports
  • synchronizing data
  • triggering Power Automate flows

One of the biggest UX problems is lack of visibility.

The user clicks a button…

…and nothing happens.

That is where the PnP Progress Control becomes extremely useful.

This article explores:

  • the real architecture of the control
  • how actions work internally
  • how to integrate it with PnPjs
  • how to integrate it with Power Automate
  • how to build a production-grade tracking model

Understanding the Progress Control

The Progress control is not a percentage bar.

This is the first thing many developers misunderstand.

It is a step-based process tracker.

Instead of:

40%

it thinks in:

Step 1
Step 2
Step 3
Step 4

Its main model:

<Progress
actions={actions}
currentActionIndex={currentActionIndex}
/>

The UI calculates progress based on action position.

Formula:

currentActionIndex / actions.length

Example:

4 actions
currentActionIndex = 2

Result:

50%

The Real Action Model

Official docs simplify this.

But internally the real interface is:

export interface IProgressAction {
title: string;
subActionsTitles?: string[];
hasError?: boolean;
errorMessage?: string;
}

This gives you:


title

Main action label:

{
title: "Reading SharePoint list"
}

subActionsTitles

Nested sub-steps:

{
title: "Processing items",
subActionsTitles: [
"Validating data",
"Calculating totals",
"Saving metadata"
]
}

Important:

These are visual only.

They do not execute.


hasError

Marks step as failed:

{
title: "Saving data",
hasError: true
}

errorMessage

Detailed error:

{
title: "Saving data",
hasError: true,
errorMessage: "Unable to save the item."
}

How State Works

This is how the control determines state:

index < currentActionIndex => completed
index = currentActionIndex => running
index > currentActionIndex => pending
hasError = true => failed

Visual:

✔ Connect
⏳ Read List
○ Process
○ Save

Mock Testing Strategy

Before production:

Always mock.

Example:

const actions = [
{ title: "Connect" },
{ title: "Read" },
{ title: "Process" },
{ title: "Save" }
];

Then simulate:

for (let i = 0; i <= actions.length; i++) {
setCurrentActionIndex(i);
}

This tests:

  • normal flow
  • completed state
  • pending state
  • long running
  • errors
  • sub actions

Mock first.

Production later.


Integrating with PnPjs

This is where Progress becomes powerful.

Architecture:

SPFx UI
Progress control
PnPjs executes operations
UI updates state

Example:

const sp = getSP(context);
setCurrentActionIndex(0);
const list = await sp.web.lists.getByTitle("Documents")();
setCurrentActionIndex(1);
const items = await sp.web.lists
.getByTitle("Documents")
.items();
setCurrentActionIndex(2);
for (const item of items) {
console.log(item.Title);
}
setCurrentActionIndex(3);

Mapping:

PnPjs OperationProgress Step
Connect SPStep 1
Read ListStep 2
Load ItemsStep 3
Process DataStep 4

Perfect match.


Real Batch Processing Pattern

Very common:

const items = await sp.web.lists
.getByTitle("Tasks")
.items();

Then:

for (const item of items) {
await sp.web.lists
.getByTitle("Tasks")
.items
.getById(item.Id)
.update({
Status: "Done"
});
}

Progress:

✔ Loading tasks
⏳ Updating tasks
○ Finalizing

This is one of the best uses.


Integrating with Power Automate

Can Progress track Power Automate?

Yes.

But indirectly.

Architecture:

SPFx
Trigger Flow
Flow executes
SPFx polls status
Progress updates

There are 4 models.


Model 1 — Fire and Forget

SPFx triggers Flow:

await fetch(flowUrl, {
method: "POST"
});

Progress:

✔ Sent
⏳ Running
✔ Done

Simple.

But no internal tracking.


Model 2 — Polling

Flow returns execution ID.

SPFx polls:

setInterval(async () => {
const result = await checkStatus();
}, 3000);

Better.


Model 3 — SharePoint Status List (Best Pattern)

This is the strongest architecture.

Flow writes into SharePoint.

List:

ExecutionId
CurrentStep
Status
Percent
Message

Example:

StepStatus
Download FilesCompleted
Parse DataRunning
Save ResultsPending

SPFx reads:

const status = await sp.web.lists
.getByTitle("FlowProgress")
.items();

Builds actions:

const actions = status.map(item => ({
title: item.Title,
hasError: item.Status === "Error",
errorMessage: item.Message
}));

This is the enterprise pattern.

Advantages:

  • historical log
  • debugging
  • audit
  • resumability
  • visibility

Best option.


Recommended Enterprise Architecture

Best production model:

User clicks button
SPFx triggers Flow
Flow creates status rows
Flow updates current step
SPFx polls list via PnPjs
Progress control renders

This combines:

SharePoint Framework
+
PnPjs
+
Power Automate
+
PnP Progress Control

This is the most scalable architecture.


When to Use This Pattern

Use it when:

✔ batch updates
✔ migrations
✔ file processing
✔ Power Automate orchestration
✔ long API calls
✔ document generation
✔ compliance jobs
✔ imports/export

Avoid for:

✖ simple button clicks
✖ tiny CRUD operations
✖ single-item updates


Final Recommendation

Think of it like this:

Progress does not execute.

Progress tells a story.

PnPjs does the work.

Power Automate does the orchestration.

SharePoint stores the truth.

That separation is what makes this architecture powerful.

Edvaldo Guimrães Filho Avatar

Published by