---
title: "Deep Linking"
description: "Send a user to a view inside a devframe (from another dock, a coding agent, or a copied URL) two ways: the hub relays a dock activation to focus a dock in place; a standalone SPA reads its URL hash to restore the view."
canonical_url: "https://devfra.me/guide/deep-linking"
---
# Deep Linking

> Send a user to a view inside a devframe (from another dock, a coding agent, or a copied URL) two ways: the hub relays a dock activation to focus a dock in place; a standalone SPA reads its URL hash to restore the view.

Send a user to a view inside a devframe (from another dock, a coding agent, or a copied URL) two ways: the hub relays a **dock activation** to focus a dock in place; a standalone SPA reads its **URL hash** to restore the view.

## Focusing a dock inside a hub

The active dock is state local to the host page. A mounted devframe, in its own iframe and RPC client, reaches it via the hub: `hub:docks:activate` switches the active dock, carrying an opaque `params` bag the target reads:

```ts
await rpc.call('hub:docks:activate', {
  dockId: 'devframes:plugin:data-inspector',
  params: { sourceId: 'my-plugin:store' },
})
```

The hub broadcasts the request and mirrors it into the [`devframe:docks:active`](https://devfra.me/guide/shared-state) slot, so a dock mounting *because* of the switch converges on it. The target subscribes, filters on its `dockId`, and reads `params`; see [Cross-iframe dock activation](https://devfra.me/guide/hub#cross-iframe-dock-activation).

Focus is one-shot: the [terminals dock](https://devfra.me/add-ons/devframes/terminals#focusing-a-session) reads `params.sessionId`, the [Data Inspector](https://devfra.me/add-ons/devframes/data-inspector#deep-linking) `params.sourceId`; a target naming something unregistered waits, then fires once. An id that never arrives is a no-op; an unknown `dockId` warns ([DF8107](https://devfra.me/errors/DF8107)).

## Standalone URL deep links

Standalone, a devframe SPA owns its own URL. Encode the shareable view in the **hash** (`#…`): it round-trips a copied link, survives reload, and stays clear of the handshake query string (`?devframe_auth_token=`). Parse with `URLSearchParams`:

```ts
// read on load
const params = new URLSearchParams(location.hash.replace(/^#/, ''))
const sourceId = params.get('source')

// write back, without stacking history entries
history.replaceState(history.state, '', `#${params.toString()}`)

// react to back/forward and manual edits
window.addEventListener('hashchange', applyState)
```

The [terminals dock](https://devfra.me/add-ons/devframes/terminals#deep-linking) keys a selection as `#id=<sessionId>`; the [Data Inspector](https://devfra.me/add-ons/devframes/data-inspector#deep-linking) encodes its workbench (`#source=…&query=…` plus filter/auto-rerun flags). `replaceState` writes never fire `hashchange`, so boot read, live listener, and write-back don't loop.

Keep credentials out of anything shareable: a handshake token belongs in the query string, scrubbed once read (as the Data Inspector does), never in a copyable hash.


## Sitemap

See the full [sitemap](https://devfra.me/sitemap.md) for all pages.
