Skip to content

Deep linking for Fabric Apps: read and write URL state from the embedded app #60

Description

Problem statement

A Fabric App runs embedded in the Fabric Portal, so it has no addressable URL of its own.
The address bar belongs to the portal, and cross-origin restrictions prevent the app from
reading or writing the parent frame's location. There is therefore no supported way to
pass state into an app at launch, or to reflect the app's current state in a link.

This removes deep linking entirely — a capability every non-embedded web application has,
and one that users of an analytical app expect.

Concretely, none of the following are possible today:

  • Sharing a specific view with a colleague by sending a link
  • Linking from Teams, email, a wiki or a SharePoint page into a particular state of an app
  • Embedding a "reopen this analysis" link in an exported file
  • Linking from a Power BI report into a deeper analysis in a Fabric App
  • Bookmarking a state in the browser
  • Preselecting filters or context when launching an app from elsewhere

We are building an analytical client for Power BI Semantic Models as a Fabric App. Sharing
a saved analysis with a colleague is one of its core workflows. Without deep linking, the
best we can offer is: the user copies a short identifier, sends it in a chat message, and
the recipient opens our app and pastes it into a dialog. That is a poor experience next to
sending a link, and it is the kind of friction users read as the platform being immature
rather than as a deliberate constraint.

Proposed solution

Extend the existing embedded-host postMessage bridge (@microsoft/fabric-embedded-host) with
a small state channel. The transport already exists, so this should be an addition to a
known protocol rather than new infrastructure.

Both directions are needed. Reading alone is not sufficient: without the ability to write,
nothing can produce a shareable link for the read side to consume.

  1. READ — launch state

    const state = await fabric.app.getLaunchState();
    // => { view: "sales-by-region", filter: "AT", period: "2026-Q2" }

    A plain key/value object supplied by the host, taken from the portal URL. Available
    before first render so the app can restore without a visible flash of default state.

  2. WRITE — reflect state in the portal URL

    fabric.app.setState({ view: "sales-by-region", filter: "AT" }); // push
    fabric.app.replaceState({ ... }); // replace

    The host owns encoding, escaping and how the state appears in its URL. The app supplies
    an opaque object; it never manipulates the URL directly.

  3. CHANGE NOTIFICATION

    fabric.app.onStateChange((state) => { ... });

    So browser back/forward can work within an app rather than navigating the portal away
    from it, which is the current behaviour and is easy for a user to trigger accidentally.

Security considerations, since they are the likely objection:

  • Values are opaque to the host and returned to the app verbatim; the host performs no
    interpretation, so there is no injection path into portal chrome.
  • A documented size limit is fine — a few KB covers every case we have. Applications
    needing more can store the payload themselves and put an identifier in the URL, which is
    exactly what a size limit should push people toward.
  • Scope the state to the app item, so one app cannot read another's.
  • Read-only launch state alone would already be a large improvement if the write side
    raises concerns; please consider shipping them separately rather than neither.

Alternatives considered

  1. Identifier plus manual entry (what we are building now)
    State is persisted in the app's own database and the user copies a short identifier that
    the recipient pastes into an "Open by identifier" dialog. It works, but it replaces a
    single click with copy, paste, and a dialog, and it fails the most common case entirely:
    sending someone a link in a chat message.

  2. Reading the parent frame's URL directly
    Not possible cross-origin, and it would be the wrong approach even if it were — apps
    should not be reading host chrome.

  3. Encoding state in the app item's own configuration
    Makes state global to the item rather than per link, so two users cannot hold different
    states at once. Unusable for sharing.

  4. A separate share service outside the app
    Adds infrastructure to solve a problem the platform is better placed to solve once, and
    every Fabric App with shareable state would have to build it independently.

Additional context

Precedent within the platform: Power BI report URLs already carry deep-link state such as
report bookmarks and filter parameters, and Fabric item URLs already carry item identifiers.
The concept exists in the portal; Fabric Apps simply have no access to it.

We are happy to test a preview implementation against a real workload — a data-heavy
analytical app where shared, reproducible state is central rather than incidental.

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions