Our field technicians were reverting to paper notes because their inspection app couldn’t retrieve equipment details in areas with no signal. After we added offline support with SaveData and LoadData, they stopped losing work to dead zones and stopped re-entering the same inspection twice because the first attempt got lost. This article shows the same pattern: how to build a Canvas app that keeps working when the connection doesn’t, using the Power Apps functions SaveData and LoadData.
Why Offline Capabilities Matter
Offline functionality lets users:
- Keep working uninterrupted when connections drop
- Pre-load critical reference data before venturing into low-connectivity areas
- Capture and store data locally to sync later
- Eliminate the frustration of lost work due to network issues
SaveData and LoadData: Core Offline Functions
SaveData writes collections to device storage, LoadData retrieves them. The key limitation: 1 MB on web browsers, device storage on mobile. Don’t store sensitive data - it’s not encrypted.
// Save data when online
SaveData(MyCollection, "MyLocalCache")
// Load it back when offline
LoadData(MyCollection, "MyLocalCache", true) // true suppresses errors on first run
SaveData’s first argument has to be an actual collection, one built with Collect or ClearCollect, not an inline Table({...}) expression evaluated on the spot. That third parameter on LoadData prevents errors when no cache exists yet, essential for first-time app launches, but it’s worth pairing with an explicit check rather than relying on it alone:
If(
IsBlankOrError(First(MyCollection).SomeField),
ClearCollect(MyCollection, Defaults);
SaveData(MyCollection, "MyLocalCache")
)
That guard catches the case where the load silently produced nothing and seeds a real default before anything downstream reads from an empty collection. Combine these with Connection.Connected to detect network status and switch between online and offline data sources.
Implementation Pattern
Planning Your Offline Strategy
Identify what data your app needs offline:
- Reference Data: Equipment specs, product catalogs (read-only lookups)
- User Input Data: Inspection forms, orders (created/updated offline)
- App State: User preferences, sync timestamps, last screen viewed
Keep the scope lean - better performance and you won’t hit storage limits.
Caching Data at Startup
Cache essential data at app startup using App.OnStart:
// App.OnStart - Load data from appropriate source
If(
Connection.Connected,
// Online: get fresh data and cache it
ClearCollect(EquipmentData, EquipmentDataSource);
ClearCollect(WorkOrders, Filter(WorkOrdersSource, AssignedTo = User().Email));
SaveData(EquipmentData, "EquipmentCache");
SaveData(WorkOrders, "WorkOrderCache"),
// Offline: load from cache
LoadData(EquipmentData, "EquipmentCache", true);
LoadData(WorkOrders, "WorkOrderCache", true)
);
Set(isOfflineMode, !Connection.Connected);
After this runs, EquipmentData and WorkOrders contain data from either the server or cache - your app doesn’t need to care which.
Tracking Offline Changes
Capture offline changes for later sync:
// Save button OnSelect
Collect(OfflineChanges, {
ID: GUID(),
RecordID: frmInspection.Item.ID,
EntityType: "Inspection",
ChangeType: "Update", // or "Create", "Delete"
ChangeData: frmInspection.Updates,
ChangeTime: Now()
});
SaveData(OfflineChanges, "PendingChanges");
This queues changes with metadata for synchronization when connectivity returns. Two details matter here: ChangeData needs Form.Updates, which captures only the fields the user actually edited, not the whole form control (which isn’t a valid record reference on its own). And RecordID is a separate field from the change’s own ID, the record being updated, not the queued change itself, which is what the sync logic below reads.
Synchronization
Sync button pushes queued changes when online:
// Sync button OnSelect
If(Connection.Connected,
ForAll(OfflineChanges,
Switch(ThisRecord.ChangeType,
"Create", Patch(InspectionsDataSource, Defaults(InspectionsDataSource), ThisRecord.ChangeData),
"Update", Patch(InspectionsDataSource, LookUp(InspectionsDataSource, ID = ThisRecord.RecordID), ThisRecord.ChangeData),
"Delete", Remove(InspectionsDataSource, LookUp(InspectionsDataSource, ID = ThisRecord.RecordID))
)
);
Clear(OfflineChanges);
SaveData(OfflineChanges, "PendingChanges");
ClearCollect(EquipmentData, EquipmentDataSource);
ClearCollect(WorkOrders, Filter(WorkOrdersSource, AssignedTo = User().Email));
SaveData(EquipmentData, "EquipmentCache");
SaveData(WorkOrders, "WorkOrderCache");
// Update sync status and timestamp
Set(lastSyncTime, Now());
Set(syncSuccessful, true);
ClearCollect(colSyncInfo, { lastSyncTime: lastSyncTime });
SaveData(colSyncInfo, "SyncInfo");
,
// **Offline**: if sync attempted without connection
Set(syncSuccessful, false)
);
This code:
- Processes each offline change based on its type (create, update, or delete)
- Clears the offline changes once processed
- Refreshes the local caches with the latest server data
- Updates the last sync timestamp
- Sets a status flag to indicate sync success or failure
This synchronization ensures that once a connection is restored, all offline changes are applied to the backend and local caches are refreshed.
User Experience Best Practices
To create a reliable offline experience, implement these UI elements:
- Connection Status Indicator: Show users when they’re working offline with a subtle banner or icon
- Last Sync Information: Display when data was last synchronized (e.g., “Last synced: 3/6/2025 10:45 AM”)
- Sync Button: Give users a way to manually trigger synchronization when they’re back online
- Sync Status Notification: After sync, show a brief confirmation like “All changes synced!” or “Sync failed: You’re offline”
These visual cues build user confidence by making the app’s state transparent. Users need to know if they’re working with the latest data or need to sync changes.
Key Considerations
As you implement offline capabilities, keep these points in mind:
Storage Limits
- Web Browser and Teams:
SaveDatais capped at 1 MB. That’s small enough that a camera control capturing even one photo can blow through it; test with the actual data types you plan to cache, not just record counts. - Mobile Player: No fixed cap, but bounded by available app memory rather than device storage generally. Microsoft’s own guidance is to expect roughly 30 to 70 MB of usable memory and to test on the actual target devices before assuming more.
- Data saved in a web browser is stored unencrypted. This is a second, separate reason not to cache sensitive data locally, beyond the size limit.
Data Prioritization
- Cache only what users truly need offline
- For large datasets, implement filtering to cache only relevant subsets
Scope: When This Pattern Isn’t the Right One
Everything above is the SaveData/LoadData pattern, which works for any data source and gives you full control over what’s cached and how sync happens. If the app runs against Dataverse specifically, Microsoft’s native offline profiles are worth evaluating first: they handle the caching, conflict resolution, and sync queue at the platform level instead of in Power Fx you write and maintain yourself. The tradeoff is less control and a narrower set of supported scenarios. For a SharePoint-backed app, or one that needs offline behavior more customized than a Dataverse profile supports, the manual pattern in this guide is still the right tool.
Conflict Handling
In multi-user systems, you may need a strategy for handling conflicts when the same record is modified by different users while offline. Options include:
- Last writer wins (simplest approach)
- Timestamp comparison to determine precedence
- Conflict flagging for manual resolution
Testing
Thoroughly test your offline implementation by:
- Enabling airplane mode to simulate complete connection loss
- Testing with slow or intermittent connections
- Verifying behavior when transitioning between online and offline
- Confirming changes sync properly when reconnected
Key Takeaways
- Plan what data is truly needed offline. Don’t cache more than the app actually uses.
- Cache on launch when connectivity is available, using
Connection.Connectedto branch between fresh data and cache. - Track offline changes in a local collection with enough metadata to replay them on sync.
- Give users a clear visual indicator of online/offline status and last-sync time.
- Test with airplane mode and intermittent connections, not just a clean online/offline toggle.
The pattern is four steps: cache when online, load from cache when offline, track changes made offline, sync when the connection returns. Our field techs went from paper forms back to the app because that pattern actually held up in the field, not because the feature existed in the abstract.
Discussion
Loading comments...
Leave a comment
Your email is required but will never be displayed publicly.