Build a Power Apps Time Tracker with SharePoint
At a Glance
- Target Audience
- Power Apps makers and Microsoft 365 teams building a small departmental time-tracking app
- Problem Solved
- Prevents duplicate or orphaned timer sessions and ensures Stop updates the exact SharePoint record created by Start.
- Use Case
- A phone-friendly Power Apps canvas app that records project sessions in a SharePoint list.
If people can start two timers, lose the active record when they close the app, or press Stop and update somebody else's row, you do not have a time tracker. You have an audit problem with two colourful buttons.
This build uses a Power Apps canvas app for the phone-friendly interface and a SharePoint list for the records. The important design choice is simple: keep the record returned by Patch when a timer starts, then update that exact record when it stops.
That removes the brittle LastCreatedID pattern from the old version of this article.
What you will build: a user selects a project, starts a session, sees a live elapsed time, stops the session and gets one completed SharePoint record with start time, end time and duration.
Fact-checked against Microsoft Learn on 7 August 2026. The formulas below are a build pattern, not a payroll or employment-compliance system.
Before you build it: decide whether SharePoint is enough
SharePoint is a sensible backend for a small departmental app when you already use Microsoft 365, the data model is simple and the app does not need transactional accounting controls.
Use Dataverse or a purpose-built time system if the records will drive pay, customer billing, legal working-time evidence or complex approvals. Those cases need stronger validation, auditing, security and change-control decisions than a short canvas-app tutorial can supply.
For this version, SharePoint stores the durable record. Power Apps supplies the interface and the live display.
1. Create the SharePoint list
Create a blank list named Project Time Records. Use simple column names when you first create them; SharePoint keeps the original internal name even if you later change the display name.
| Column | Type | Required? | Purpose |
|---|---|---|---|
| Title | Single line of text | Yes | A readable label for the entry |
| Project | Choice | Yes | The project selected by the user |
| StartedAt | Date and time | Yes | When the session began |
| EndedAt | Date and time | No | When the session ended |
| DurationSeconds | Number | No | Completed duration in seconds |
| SessionOwner | Single line of text | Yes | Lower-case email address used to find the user's open session |
| SessionState | Single line of text | Yes | Running or Complete |
| Notes | Multiple lines of text | No | Optional work note |
Add your real project names to the Project choices.
Index SessionOwner and SessionState if this list could become large. The app will use equality checks on those text fields; Microsoft documents = and LookUp as delegable for SharePoint text columns. You should still fix any delegation warning Power Apps shows rather than assuming a formula will scale.
You do not need to add an ID column. SharePoint creates one automatically.
Why store seconds instead of a formatted duration?
DateDiff returns a whole number in the unit you request. Saving seconds preserves enough precision to display hh:mm:ss and lets reports aggregate a normal number.
Store the raw start and end values as well. That gives you something to audit if the displayed duration ever looks wrong.
2. Create the canvas app
In Power Apps, create a canvas app and add the Project Time Records SharePoint list as a data source.
Microsoft's current list-generated app starts from a single responsive screen built with containers. You can use that as a base or create a blank canvas app. For a phone-friendly layout, use responsive containers and test the published app on the actual device sizes your team uses.
Add these controls and give them useful names:
| Control | Suggested name | Purpose |
|---|---|---|
| Dropdown | ddProject |
Select a project |
| Button | btnStart |
Create the running record |
| Button | btnStop |
Complete that record |
| Label | lblElapsed |
Show elapsed time |
| Text input | txtNotes |
Optional work note |
| Timer | tmrClock |
Refresh the displayed current time |
| Gallery | galMyEntries |
Show the user's recent entries |
Set ddProject.Items to:
Choices('Project Time Records'.Project)
3. Restore an open session when the screen loads
A user may close the app while a timer is running. The app therefore needs to recover their open record from SharePoint, not rely only on an in-memory variable.
Set the screen's OnVisible property to:
Set(
varActiveEntry,
LookUp(
'Project Time Records',
SessionOwner = Lower(User().Email) &&
SessionState = "Running"
)
);
Set(varTimerRunning, !IsBlank(varActiveEntry));
Set(varRunStartedAt, varActiveEntry.StartedAt);
Set(varNow, Now())
This expects at most one running entry per user. If the lookup finds two, that is a data-quality problem you should surface to an administrator rather than silently guessing which row is correct.
For a controlled rollout, consider a scheduled check that reports duplicate open sessions and sessions left running beyond a sensible threshold.
4. Start the timer and keep the created record
Microsoft documents that Patch returns the record it created or changed, including values generated by the data source. Store that result in varActiveEntry.
Set btnStart.OnSelect to:
If(
IsBlank(ddProject.Selected.Value),
Notify("Choose a project first.", NotificationType.Warning),
!IsBlank(varActiveEntry),
Notify("You already have a running session.", NotificationType.Warning),
IfError(
Set(
varActiveEntry,
Patch(
'Project Time Records',
Defaults('Project Time Records'),
{
Title: User().FullName & " - " &
Text(Now(), "[$-en-GB]yyyy-mm-dd hh:mm"),
Project: {Value: ddProject.Selected.Value},
StartedAt: Now(),
SessionOwner: Lower(User().Email),
SessionState: "Running",
Notes: txtNotes.Text
}
)
);
Set(varRunStartedAt, varActiveEntry.StartedAt);
Set(varNow, Now());
Set(varTimerRunning, true);
Notify("Timer started.", NotificationType.Success),
Notify(
"The timer was not started: " & FirstError.Message,
NotificationType.Error
)
)
)
The old formula created a row but then referred to an undefined LastCreatedID. This version retains the actual SharePoint record returned by Patch, so the stop action has an unambiguous target.
5. Stop the same record
Set btnStop.OnSelect to:
If(
IsBlank(varActiveEntry),
Notify("There is no running session to stop.", NotificationType.Warning),
With(
{stoppedAt: Now()},
IfError(
Set(
varClosedEntry,
Patch(
'Project Time Records',
varActiveEntry,
{
EndedAt: stoppedAt,
DurationSeconds: DateDiff(
varActiveEntry.StartedAt,
stoppedAt,
TimeUnit.Seconds
),
SessionState: "Complete"
}
)
);
Set(varTimerRunning, false);
Set(varActiveEntry, Blank());
Set(varRunStartedAt, Blank());
Refresh('Project Time Records');
Reset(txtNotes);
Notify("Time entry saved.", NotificationType.Success),
Notify(
"The time entry was not saved: " & FirstError.Message,
NotificationType.Error
)
)
)
)
Do not clear varActiveEntry before Patch succeeds. If SharePoint is unavailable or the user lacks permission, keeping the record in memory gives them a chance to retry instead of pretending the session was saved.
6. Show a live elapsed time
The Timer control is only a refresh mechanism here. SharePoint remains the record of truth.
Set these properties on tmrClock:
Duration: 1000
Repeat: true
Start: varTimerRunning
Visible: false
OnTimerEnd: Set(varNow, Now())
Set lblElapsed.Text to:
If(
IsBlank(varRunStartedAt),
"00:00:00",
With(
{
elapsedSeconds: DateDiff(
varRunStartedAt,
varNow,
TimeUnit.Seconds
)
},
Text(RoundDown(elapsedSeconds / 3600, 0), "00") & ":" &
Text(RoundDown(Mod(elapsedSeconds, 3600) / 60, 0), "00") & ":" &
Text(Mod(elapsedSeconds, 60), "00")
)
)
This avoids using a time-of-day value for a duration. It also keeps counting beyond 24 hours, although an open session that long should probably trigger an exception report.
7. Make invalid actions impossible
Set btnStart.DisplayMode to:
If(IsBlank(varActiveEntry), DisplayMode.Edit, DisplayMode.Disabled)
Set btnStop.DisplayMode to:
If(IsBlank(varActiveEntry), DisplayMode.Disabled, DisplayMode.Edit)
Disable the project dropdown while a timer is running too. Otherwise the screen suggests that changing the project will change an already-created record.
If project reassignment is a genuine requirement, make it an explicit edit operation with a visible audit decision.
8. Show the current user's entries
Set the gallery's Items property to a delegable query and keep the returned columns narrow:
SortByColumns(
Filter(
'Project Time Records',
SessionOwner = Lower(User().Email)
),
"StartedAt",
SortOrder.Descending
)
Power Apps processes a nondelegable query locally and can return an incomplete result once the source exceeds the configured row limit. Microsoft explicitly recommends paying attention to delegation warnings. A time-record list grows quickly, so this is not a cosmetic warning.
9. Share and test the whole system
Sharing the canvas app is not enough. Users also need the appropriate permissions on the SharePoint list, and the app's data connection must work for them.
Test with a normal user account, not just the maker account.
Use this acceptance checklist:
- A user cannot start without choosing a project.
- A user cannot create two running sessions through normal app use.
- Closing and reopening the app restores the open session.
- Stop updates the exact record created by Start.
- A failed
Patchdisplays an error and does not fake success. - The elapsed display survives more than 60 minutes.
- Each user sees only the entries the app is intended to show.
- The list's actual permissions match the privacy promise made in the app.
- No formula has an unresolved delegation warning against production-sized data.
- Times and reports have been checked with users in the time zones you support.
Also test the published app in narrow and wide windows. Microsoft notes that responsive behaviour needs testing on real devices or browser sizes after publishing.
What this app does not prove
This build proves a practical start/stop record pattern. It does not prove payroll accuracy, protect SharePoint rows from every authorised editor, stop an administrator changing data, handle offline writes or supply a legal audit trail.
Those are separate requirements.
If you need approvals, exception handling or payroll export, document the rules first. Then decide whether to extend this app, add a Power Automate process, or move the data to a platform designed for those controls.
For more Power Apps foundations, see how App.OnStart works—and when not to put everything in it.
Keep building with fewer mystery failures
The Power Apps Builders Space is where we keep practical Power Apps patterns, checks and build decisions together.
Microsoft sources used
- Create a canvas app from a SharePoint or Microsoft List
- Patch function
- DateDiff and related date functions
- Timer control
- SharePoint delegation support in Power Apps
- Delegation and query limits
- Create responsive canvas-app layouts
Source boundary: Microsoft documents the product functions and limits cited above. The complete time-tracker design is Collab365's implementation pattern and still needs testing against your tenant, permissions, data volume and business rules.
