Introduction
Field workers, warehouse staff, and mobile users often work in areas with poor or no internet connectivity. I've built offline-capable Power Apps for construction sites, remote inspections, delivery trucks, and manufacturing floors—environments where connectivity is unreliable or non-existent. These apps need to continue functioning regardless of network status.
This article covers the patterns, strategies, and implementation details for building Power Apps that gracefully handle offline scenarios while maintaining data integrity and a smooth user experience.
Understanding Offline Capabilities
What "Offline" Means in Power Apps
Power Apps has three modes related to connectivity:
- Online: Full connectivity, real-time data access
- Offline: No connectivity, local data storage only
- Intermittent: Connectivity comes and goes (most challenging)
Power Apps Offline Capabilities
Power Apps Mobile supports offline mode natively for Dataverse tables. Key features:
- Automatic data download when online
- Local storage on device
- Queue changes made offline
- Automatic sync when connectivity restored
- Conflict detection and resolution
Limitations
- Only works with Dataverse (not SharePoint, SQL, etc.)
- Requires Power Apps Mobile app (not web browser)
- Limited to 10 tables per app
- Max 1 GB local storage per app
- Must explicitly enable offline mode
- Complex relationships may not sync properly
Enabling Offline Mode
Step 1: Configure App for Offline
In Power Apps Studio:
1. Settings → Upcoming features → Experimental
2. Enable "Offline data sync"
3. Save and publish app
Step 2: Configure Tables for Offline
In Power Apps Studio:
1. Settings → General
2. Offline data sync
3. Add tables needed offline (max 10)
4. Configure:
- Sync mode: Download all records or filter
- Download interval
- Attachment sync
Step 3: Define Offline Profile
In Power Apps Admin Center:
1. Select environment
2. Settings → Mobile offline
3. Create offline profile
4. Add tables and relationships
5. Set filters (which records to download)
6. Assign to users/teams
Example Profile Configuration:
Profile: "Field Inspector"
Tables:
- Work Orders
Filter: Status = Active AND AssignedTo = Current User
Include: Last 30 days
- Accounts (Related)
Filter: Related to Work Orders
- Contacts (Related)
Filter: Related to Accounts
- Inspections (Related)
Filter: Related to Work Orders
Include: Attachments
Total estimated size: 50 MB per user
Data Sync Patterns
Pattern 1: Full Download
Download all data that user might need, sync periodically.
When to Use:
- Small datasets (< 10,000 records)
- Predictable offline periods
- Users need comprehensive access
Configuration:
Offline Profile:
- Download: All records matching filter
- Sync interval: Every 8 hours
- Force sync on app launch
Example filter: "Created in last 90 days"
Pattern 2: Filtered Download
Download only records relevant to current user or assignment.
When to Use:
- Large datasets
- User-specific workflows
- Limited device storage
Configuration:
Filter examples:
- AssignedTo equals Current User
- Territory equals User's Territory
- Status equals Active OR In Progress
- ModifiedOn within last 30 days
Reduces download from 100k records to 500 per user
Pattern 3: On-Demand Download
Download specific records when user needs them (while online), keep cached offline.
Implementation:
// When user searches for work order
If(Connection.Connected,
// Online: fetch from Dataverse
ClearCollect(colWorkOrders,
Filter(WorkOrders,
WorkOrderNumber = txtSearch.Text
)
);
// Save to local collection
SaveData(colWorkOrders, "WorkOrders")
,
// Offline: load from local storage
LoadData(colWorkOrders, "WorkOrders")
)
Detecting Connectivity
Using Connection.Connected
// Display connectivity status
Label:
Text: If(Connection.Connected, "Online ✓", "Offline ⚠")
Color: If(Connection.Connected, Green, Red)
// Conditional logic
If(Connection.Connected,
// Do online operations
Patch(Accounts, ...),
// Do offline operations
Collect(colPendingChanges, ...)
)
Real-Time Connectivity Indicator
// Add to App.OnStart
Set(varIsOnline, Connection.Connected);
// Timer control (every 5 seconds)
Timer:
Duration: 5000
Repeat: true
OnTimerEnd: Set(varIsOnline, Connection.Connected)
// Visual indicator
Icon:
Icon: If(varIsOnline, Icon.CheckBadge, Icon.Warning)
Color: If(varIsOnline, Green, Orange)
Tooltip: If(varIsOnline, "Connected", "Offline mode")
Handling User Input Offline
Queuing Changes
When offline, queue user actions for later sync.
// OnSelect of Save button
If(Connection.Connected,
// Online: Save directly
Patch(WorkOrders, ThisItem, {
Status: "Completed",
CompletedDate: Now()
});
Notify("Saved successfully", NotificationType.Success)
,
// Offline: Queue for sync
Collect(colPendingChanges, {
RecordId: ThisItem.Id,
Table: "WorkOrder",
Action: "Update",
Changes: {
Status: "Completed",
CompletedDate: Now()
},
Timestamp: Now()
});
SaveData(colPendingChanges, "PendingChanges");
Notify("Saved locally. Will sync when online.", NotificationType.Information)
)
Automatic Sync When Online
// App.OnStart
If(Connection.Connected,
// Check for pending changes
LoadData(colPendingChanges, "PendingChanges");
If(!IsEmpty(colPendingChanges),
// Process pending changes
ForAll(colPendingChanges,
Switch(Table,
"WorkOrder", Patch(WorkOrders,
LookUp(WorkOrders, Id = RecordId),
Changes
),
"Inspection", Patch(Inspections,
LookUp(Inspections, Id = RecordId),
Changes
)
)
);
// Clear pending changes
Clear(colPendingChanges);
RemoveData("PendingChanges");
Notify(
CountRows(colPendingChanges) & " changes synced",
NotificationType.Success
)
)
)
Conflict Resolution
Understanding Conflicts
Conflicts occur when:
- User edits record offline
- Another user edits same record online
- Both changes need to merge
Conflict Resolution Strategies
Strategy 1: Last Write Wins (Default)
Dataverse default behavior:
- Latest change overwrites previous
- Simple but can lose data
- Suitable for low-conflict scenarios
No code needed - automatic
Strategy 2: Server Wins
// Before patching, check server version
Set(varServerRecord,
LookUp(WorkOrders, Id = varLocalRecord.Id)
);
If(varServerRecord.ModifiedOn > varLocalRecord.ModifiedOn,
// Server has newer version
Notify("This record was updated by someone else. Loading latest version.",
NotificationType.Warning);
// Discard local changes and reload
Set(varLocalRecord, varServerRecord)
,
// Local is newer, safe to save
Patch(WorkOrders, varLocalRecord, {
Status: "Completed"
})
)
Strategy 3: Manual Merge (Most Robust)
// Detect conflict
If(varServerRecord.ModifiedOn > varLocalRecord.ModifiedOn,
// Show conflict resolution screen
Set(varConflictMode, true);
Set(varConflictLocalVersion, varLocalRecord);
Set(varConflictServerVersion, varServerRecord)
)
// Conflict Resolution Screen:
// Show both versions side-by-side
// Let user choose which fields to keep
Gallery showing differences:
Items: [
{
Field: "Status",
Local: varConflictLocalVersion.Status,
Server: varConflictServerVersion.Status,
Selected: varConflictServerVersion.Status
},
{
Field: "Notes",
Local: varConflictLocalVersion.Notes,
Server: varConflictServerVersion.Notes,
Selected: varConflictLocalVersion.Notes
}
]
// On Resolve button:
Patch(WorkOrders, varConflictServerVersion, {
Status: galConflicts.Selected.Status,
Notes: galConflicts.Selected.Notes
})
Managing Attachments Offline
Download Attachments
// Configure in offline profile
Include attachments: Yes
Max size per attachment: 10 MB
Max attachments per record: 20
// Attachments download with records
// Stored locally in app data
Adding Attachments Offline
// Capture photo
Set(varPhoto, Camera1.Photo);
If(Connection.Connected,
// Online: Upload immediately
Patch(Inspections, ThisItem, {
Photo: varPhoto
})
,
// Offline: Store locally
Collect(colLocalPhotos, {
InspectionId: ThisItem.Id,
Photo: varPhoto,
Timestamp: Now()
});
SaveData(colLocalPhotos, "LocalPhotos");
Notify("Photo saved locally", NotificationType.Information)
)
// On sync
ForAll(colLocalPhotos,
Patch(Inspections,
LookUp(Inspections, Id = InspectionId),
{Photo: Photo}
)
);
Clear(colLocalPhotos);
RemoveData("LocalPhotos")
User Experience Best Practices
1. Clear Visual Feedback
// Status banner at top of app
Container:
Visible: !Connection.Connected
Fill: RGBA(255, 165, 0, 0.1)
Label:
Text: "⚠ Working offline. Changes will sync when connected."
Color: Orange
// Pending changes indicator
Label:
Text: CountRows(colPendingChanges) & " changes pending sync"
Visible: CountRows(colPendingChanges) > 0
2. Manual Sync Button
Button:
Text: "Sync Now"
Icon: Icon.Sync
Visible: Connection.Connected And CountRows(colPendingChanges) > 0
OnSelect:
// Process pending changes
ForAll(colPendingChanges,
Patch(...)
);
Clear(colPendingChanges);
RemoveData("PendingChanges");
Notify("Synced successfully", NotificationType.Success)
3. Download Status
// Show download progress
Label:
Text: "Downloading data for offline use..."
Visible: varDownloading
ProgressBar:
Value: varDownloadProgress
Visible: varDownloading
4. Graceful Degradation
// Disable features requiring connectivity
Button (Submit to API):
DisplayMode: If(Connection.Connected, Edit, Disabled)
Label (Help text):
Text: "This feature requires internet connection"
Visible: !Connection.Connected
Testing Offline Scenarios
Testing Checklist
- ✅ App launches while offline
- ✅ Can view previously synced data
- ✅ Can create new records offline
- ✅ Can edit existing records offline
- ✅ Changes queue properly
- ✅ Sync works when coming back online
- ✅ Conflicts detected and resolved
- ✅ Attachments handled correctly
- ✅ UI clearly shows offline status
- ✅ No errors or crashes
Testing Techniques
1. Airplane Mode Testing
1. Enable offline mode in app
2. Sync data while online
3. Enable airplane mode on device
4. Test all app functionality
5. Make changes
6. Disable airplane mode
7. Verify sync
2. Intermittent Connectivity
1. Toggle airplane mode on/off during use
2. Test app behavior during transitions
3. Ensure no data loss
4. Verify sync recovers gracefully
3. Conflict Scenarios
1. User A: Edit record offline
2. User B: Edit same record online
3. User A: Come back online and sync
4. Verify conflict detection
5. Test resolution process
Performance Optimization
1. Minimize Data Downloaded
// Download only necessary columns
Configure offline profile:
- Don't include large text fields unless needed
- Exclude attachments if not critical
- Use tight filters
- Limit related records depth
Example: Instead of all accounts, download only:
- Active accounts
- In user's territory
- Modified in last 30 days
Result: 100k records → 500 records per user
2. Use Collections for Offline Data
// On app start
ClearCollect(colWorkOrders,
Filter(WorkOrders, Status = "Active")
);
// Use collection in app (faster than queries)
Gallery:
Items: colWorkOrders
// Update collection locally
Patch(colWorkOrders, ThisItem, {Status: "Complete"});
// Sync to server when online
If(Connection.Connected,
ForAll(colWorkOrders,
Patch(WorkOrders, ThisItem, ThisRecord)
)
)
3. Optimize Images
// Compress photos before storing
Set(varPhoto, Camera1.Photo);
Set(varCompressedPhoto,
Compress(varPhoto,
Width: 1024,
Height: 768,
Quality: 0.7
)
);
// Store compressed version
Collect(colPhotos, varCompressedPhoto)
Common Patterns and Solutions
Pattern 1: Work Order Management
Use Case: Field technicians complete work orders
Offline Profile:
- Today's assigned work orders
- Related customer accounts
- Product catalog (read-only)
- Previous work history (read-only)
Actions Offline:
- View work order details
- Update status
- Add notes
- Take photos
- Complete inspections
Sync Strategy:
- Download assignments at start of day
- Queue all changes
- Sync at end of day or when connected
Pattern 2: Inspection App
Use Case: Safety inspections in remote locations
Offline Profile:
- Inspection templates
- Current site assignments
- Previous inspection results
Actions Offline:
- Complete inspection checklist
- Take photos of issues
- Record measurements
- Add comments
- Digital signature
Sync Strategy:
- Full inspection syncs as single unit
- Photos upload separately (can be large)
- Conflict unlikely (one inspector per site)
Pattern 3: Inventory Counting
Use Case: Warehouse inventory counts
Offline Profile:
- Product master list
- Current inventory levels
- Assigned count zones
Actions Offline:
- Scan barcodes
- Enter counts
- Note discrepancies
- Take photos of issues
Sync Strategy:
- Manual sync required (to prevent mid-count updates)
- Batch upload all counts
- Conflict resolution: Most recent count wins
Troubleshooting Common Issues
Issue 1: Data Not Downloading
Possible causes:
- Offline profile not assigned to user
- Filters too restrictive (no records match)
- Device storage full
- Sync disabled in app
Solution:
- Verify profile assignment
- Test filters in Advanced Find
- Check device storage
- Enable sync in app settings
Issue 2: Changes Not Syncing
Possible causes:
- Conflict preventing sync
- Network timeout
- Record locked by another process
- Validation rules failing
Solution:
- Check for conflicts in sync log
- Increase timeout
- Retry after wait
- Review validation rules
Issue 3: App Slow Offline
Possible causes:
- Too much data downloaded
- Not using collections
- Complex formulas in galleries
- Large attachments
Solution:
- Reduce offline data scope
- Use ClearCollect for performance
- Simplify formulas
- Compress attachments
Best Practices Summary
Design
- Plan for offline from the start
- Download only essential data
- Design for intermittent connectivity
- Provide clear visual feedback
- Handle conflicts gracefully
Implementation
- Use Connection.Connected for logic branching
- Queue changes when offline
- Implement automatic sync
- Compress images and attachments
- Use collections for performance
Testing
- Test with airplane mode
- Test intermittent connectivity
- Test conflict scenarios
- Test with realistic data volumes
- Test on actual devices (not just emulator)
Conclusion
Building offline-capable Power Apps requires careful planning around data sync, conflict resolution, and user experience. The built-in offline capabilities in Power Apps Mobile handle much of the complexity, but you need to design your app with offline scenarios in mind from the start.
Key success factors: minimize data downloaded, provide clear connectivity status, queue changes when offline, handle conflicts gracefully, and test thoroughly in realistic conditions. The field workers using your app will depend on it working regardless of network availability.
Remember: offline capability isn't just about working without internet—it's about providing a reliable, consistent experience even when connectivity is intermittent or unreliable. Build for the worst-case scenario, and your users will thank you.