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.

Building a deep link is straightforward:

  1. Base URL: Start with your app’s base web link from the Power Apps portal:

    https://apps.powerapps.com/play/e/<EnvironmentID>/a/<AppID>
    
  2. 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 Action parameter is “StatusUpdate”, we start on the StatusUpdateScreen
  • Else if there’s a ProjectID provided, we navigate to the ProjectDetailsScreen for 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.StartScreen formula 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’s OnVisible in addition to App.StartScreen
  • Check if offline mode is enabled, which might affect parameter handling
  • Verify the mobile app is updated to the latest version

Best Practices

  1. Use App.StartScreen for Navigation Control: Always use this property for initial navigation instead of Navigate() in App.OnStart.

  2. Always Have a Fallback: Plan for scenarios where no parameters are provided by defaulting to a logical screen.

  3. 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())
    )
    
  4. Test Across Platforms: Verify your deep links work consistently on web, mobile apps, and within Teams.

  5. 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.

  1. Authentication Still Required: Deep links don’t bypass authentication. Users must sign in and have appropriate permissions to access the app.

  2. Validate Parameters: Treat URL parameters as untrusted input and validate them before use.

  3. 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.StartScreen to control navigation based on URL parameters, not Navigate() in App.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.

Additional Resources