Skip to content
You are reading the v2 docs, currently in beta.V1 docs
oRPC
Esc
navigateopen⌘Jpreview
On this page

Browser Adapter

Use oRPC for typesafe communication between browser scripts via the Message Port Adapter.

Enable typesafe communication between browser scripts using the Message Port Adapter.

Between Extension Scripts

To set up communication between scripts in a browser extension (e.g. background, content, popup), configure one script to listen for connections and upgrade them, and another to initiate the connection.

import { onError } from '@orpc/server'
import { RPCHandler } from '@orpc/server/message-port'

const handler = new RPCHandler(router, {
  interceptors: [
    onError((error) => {
      console.error(error)
    }),
  ],
})

browser.runtime.onConnect.addListener((port) => {
  handler.upgrade(port, {
    context: {} // Provide initial context if needed
  })
})
import { RPCLink } from '@orpc/client/message-port'

const port = browser.runtime.connect()

const link = new RPCLink({
  port,
})

Window to Window

To enable communication between two window contexts (e.g. parent and popup), one must listen and upgrade the port, and the other must initiate the connection.

import { onError } from '@orpc/server'
import { RPCHandler } from '@orpc/server/message-port'

const handler = new RPCHandler(router, {
  interceptors: [
    onError((error) => {
      console.error(error)
    }),
  ],
})

window.addEventListener('message', (event) => {
  if (event.data instanceof MessagePort) {
    handler.upgrade(event.data, {
      context: {} // Provide initial context if needed
    })

    event.data.start()
  }
})

window.open('/example/popup', 'popup', 'width=680,height=520')
import { RPCLink } from '@orpc/client/message-port'

const { port1: serverPort, port2: clientPort } = new MessageChannel()

window.opener.postMessage(serverPort, '*', [serverPort])

const link = new RPCLink({
  port: clientPort,
})

clientPort.start()

Advanced Relay Pattern

In some advanced cases, direct communication between scripts isn’t possible. For example, a content script running in the “MAIN” world cannot directly communicate with the background script using browser.runtime or chrome.runtime APIs.

To work around this, you can use a relay pattern: an additional content script running in the “ISOLATED” (default) world relays messages between the two contexts, enabling communication where direct access is restricted.

window.addEventListener('message', (event) => {
  if (event.data instanceof MessagePort) {
    const port = browser.runtime.connect()

    // Relay `message` and `close/disconnect` events between the MessagePort and runtime.Port

    event.data.addEventListener('message', (event) => {
      port.postMessage(event.data)
    })

    event.data.addEventListener('close', () => {
      port.disconnect()
    })

    port.onMessage.addListener((message) => {
      event.data.postMessage(message)
    })

    port.onDisconnect.addListener(() => {
      event.data.close()
    })

    event.data.start()
  }
})
import { onError } from '@orpc/server'
import { RPCHandler } from '@orpc/server/message-port'

const handler = new RPCHandler(router, {
  interceptors: [
    onError((error) => {
      console.error(error)
    }),
  ],
})

browser.runtime.onConnect.addListener((port) => {
  handler.upgrade(port, {
    context: {} // Provide initial context if needed
  })
})
import { RPCLink } from '@orpc/client/message-port'

const { port1: serverPort, port2: clientPort } = new MessageChannel()

window.postMessage(serverPort, '*', [serverPort])

const link = new RPCLink({
  port: clientPort,
})

clientPort.start()

Last updated on August 25, 2026

Was this page helpful?