We built QR codes that opened our equipment inspection app directly to a specific item’s details screen, cutting field technicians out of searching through hundreds of items. We used the same pattern for expense approvals: notification emails now link straight to the relevant approval screen instead of dropping managers on a home screen to go hunt for the right record themselves. That’s what deep linking does: instead of routing users through multiple screens to reach one destination, a single link puts them there directly.
Why Deep Linking Matters
Effective deep linking brings several key benefits:
- Improved User Experience: Users reach their destination with a single click, without tedious manual navigation
- Fewer Abandoned Flows: There’s no multi-screen path to abandon partway through
- Better Integration: Your app becomes part of a cohesive ecosystem, connecting with emails, chats, and other tools
- Time Savings: Users spend less time clicking around and more time on actual tasks
Key Concepts in Deep Linking
Every Canvas app has a base web URL that accepts query parameters, and Param() is the Power Fx function that reads them back out at runtime:
https://apps.powerapps.com/play/e/<EnvironmentID>/a/<AppID>?Page=Details&ItemID=123
Param("ItemID") returns "123" in that example. Everything else in this guide is just what you do with that value once you have it.
Implementing Deep Linking in Power Apps
Setting up deep linking requires configuring your app to respond to URL parameters. Let’s go through the essential steps:
Step 1: Use the App.StartScreen Property
The App.StartScreen property determines the initial screen when the app opens:
App.StartScreen = Switch(
Param("Page"),
"Details", DetailsScreen,
"Settings", SettingsScreen,
HomeScreen
)
When the app launches, it checks the Page parameter. If Param("Page") equals "Details", the app opens on DetailsScreen. If it’s "Settings", it opens on SettingsScreen. Otherwise, it defaults to HomeScreen.
Using App.StartScreen is significantly more reliable than trying to use Navigate() in App.OnStart. Microsoft has deprecated calling Navigate() in App.OnStart and disabled it by default for new apps (a backward-compatibility setting can re-enable it for existing apps that still depend on it), which makes App.StartScreen the recommended approach for anything new.
Step 2: Retrieve Parameters Using Param()
Once the correct screen opens, capture the parameter values for use within that screen:
If(
!IsBlank(Param("ItemID")),
Set(itemID, Param("ItemID")),
Set(itemID, Blank())
)
This creates a global variable itemID and stores the ItemID parameter value from the URL. Always check for blank or missing values to prevent errors – a small defensive step that saves headaches.
Tip: If you expect a numeric ID or GUID, convert it to the proper data type when storing it. For example, use Value(Param("OrderID")) for numeric IDs or GUID(Param("RecordID")) for Dataverse GUIDs.
Step 3: Build Logic Based on Parameters
Use the parameter values to filter data or perform navigation logic inside the app:
If(
!IsBlank(itemID),
Filter(ItemsDataSource, ID = Value(itemID)),
ItemsDataSource
)
In this example, if itemID is not blank, we filter the ItemsDataSource to only show the item with that ID. Otherwise, we show all items. This approach could go in a gallery’s Items property to display either a single record or a full list.
With these three steps, your app is now “deep link aware” – it can pick a start screen based on a parameter, grab parameter values, and adjust data accordingly.
Creating Deep Links
Building a deep link is straightforward:
Base URL: Start with your app’s base web link from the Power Apps portal:
https://apps.powerapps.com/play/e/<EnvironmentID>/a/<AppID>Add Parameters: Append query parameters to define the context, beginning the first parameter with
?and any additional parameters with&:https://apps.powerapps.com/play/e/<EnvironmentID>/a/<AppID>?Page=Details&ItemID=456
For consistency and maintainability, avoid scattering hard-coded URLs throughout your systems. Instead, create a helper formula or central place to build your deep links – this makes updates much easier if your app URL changes.
Real-World Implementation Example
Here’s a practical scenario where deep linking made a significant impact in our organization:
Approval Process Integration
For our expense approval system, we send emails with deep links that take approvers directly to the specific expense report needing their attention:
"Please review this expense report: https://apps.powerapps.com/play/e/<EnvironmentID>/a/<AppID>?Screen=Approval&ExpenseID=" & ExpenseID
Inside the app, we handle this with a combination of App.StartScreen and parameter logic:
// In App.StartScreen formula
App.StartScreen = If(Param("Screen") = "Approval", ApprovalScreen, HomeScreen);
// In ApprovalScreen's OnVisible property
Set(
currentExpense,
LookUp(Expenses, ID = Value(Param("ExpenseID")))
);
Param() always returns text, and ID is a number column, so the lookup needs Value() around it, the same wrapper the earlier step in this guide recommends. Without it, the comparison silently returns no match instead of an error, which is a worse failure mode because nothing in the app tells you why the screen came up blank. This is the same shape used in production: ID = Value(Param("ID")) feeding App.StartScreen, so a bare number in the URL resolves to a real record on load rather than a blank comparison.
First, the app navigates to the ApprovalScreen if the URL’s Screen parameter equals “Approval”. Then, when the ApprovalScreen loads, we use the ExpenseID parameter to look up the specific expense record.
Managers no longer had to search for items at all; they landed directly on the exact expense that required their attention.
Handling Multiple Parameters
For more complex scenarios, you might need to pass multiple parameters and handle their combinations. Here’s a pattern we use in our task management app:
App.StartScreen = Switch(
true,
Param("Action") = "StatusUpdate", StatusUpdateScreen,
!IsBlank(Param("ProjectID")), ProjectDetailsScreen,
HomeScreen
);
Using Switch(true, ...) evaluates multiple conditions in order:
- If the
Actionparameter is “StatusUpdate”, we start on theStatusUpdateScreen - Else if there’s a
ProjectIDprovided, we navigate to theProjectDetailsScreenfor that project - Otherwise, we default to
HomeScreen
This approach allows your deep links to carry significant context, creating a tailored experience based on multiple parameters.
Troubleshooting Common Issues
When implementing deep linking, you might encounter a few challenges. Here are the most common issues and how to fix them:
Parameters Not Being Received
Issue: The app opens, but isn’t receiving the parameter values.
Solutions:
- Verify parameter name casing (they’re case-sensitive)
- Check URL encoding for special characters
- Ensure the user has access to the app
App Opens on the Wrong Screen
Issue: The app loads but stays on the default screen.
Solution:
- Double-check your
App.StartScreenformula logic - Add a temporary debug label that shows
Param("YourParamName")to verify the value - Ensure screen names in your formulas exactly match your actual screens
Works on Web but Not on Mobile
Issue: The link works on desktop but not on mobile devices.
Why: The Power Apps mobile player keeps the app instance running in the background instead of fully terminating it. If the app is already warm when a user taps a new deep link, the player can bring the existing session forward instead of cold-starting it, which means App.OnStart and App.StartScreen never re-evaluate and the new Param() values are never read. This is the single most common “it works on web but not mobile” report I’ve seen, and it’s a platform caching behavior, not a bug in your formula.
Solution:
- Ensure the user fully closes the app before testing a new link, or account for it by re-checking
Param()in a screen’sOnVisiblein addition toApp.StartScreen - Check if offline mode is enabled, which might affect parameter handling
- Verify the mobile app is updated to the latest version
Best Practices
Use
App.StartScreenfor Navigation Control: Always use this property for initial navigation instead ofNavigate()inApp.OnStart.Always Have a Fallback: Plan for scenarios where no parameters are provided by defaulting to a logical screen.
Validate Parameters: Never assume the URL parameters are perfect. Validate data types and check for missing values:
If( !IsBlank(Param("ItemID")) && !IsNumeric(Param("ItemID")), Notify("Invalid item ID provided", NotificationType.Error); Set(itemID, Blank()) )Test Across Platforms: Verify your deep links work consistently on web, mobile apps, and within Teams.
Avoid Sensitive Data in URLs: Never embed passwords, personal information, or sensitive data as parameters.
Security Considerations
The specific failure mode to design against: authentication and authorization are not the same thing, and deep links make it easy to accidentally ship only the first one. A user can be fully authenticated, land on ApprovalScreen via a valid deep link with a record ID they were never supposed to have, and see the data anyway, because the screen trusted the URL parameter instead of independently checking whether that user has rights to that specific record. This is the same class of bug as an IDOR (insecure direct object reference) vulnerability in a web app; a deep link is just a URL, and it deserves the same skepticism.
Authentication Still Required: Deep links don’t bypass authentication. Users must sign in and have appropriate permissions to access the app.
Validate Parameters: Treat URL parameters as untrusted input and validate them before use.
Enforce Record-Level Security Independently of the Link: Every screen that loads data based on a
Param()value must re-check that the current user has rights to that specific record, in the app’s own logic, not assume that having a valid-looking link implies authorization.
Key Takeaways
- Use
App.StartScreento control navigation based on URL parameters, notNavigate()inApp.OnStart(deprecated, and off by default for new apps). - Validate every incoming parameter. URL parameters are untrusted input, not just missing-value risks.
- Build a helper formula for constructing deep links instead of hard-coding URLs across systems.
- Test across web, mobile, and Teams. The same link doesn’t always behave identically on all three.
Find one high-friction scenario in an existing app, one screen or record users are manually hunting for, and wire a deep link to it. The approval workflow above is the same fix: one link, and the manual hunting stops being part of the job.
Discussion
Loading comments...
Leave a comment
Your email is required but will never be displayed publicly.