Skip to main content
WebviewJS provides two complementary mechanisms for communication between your web page and Node.js. The first is raw IPC: the page calls window.ipc.postMessage() and Node receives the bytes through webview.onIpcMessage(). The second is the higher-level webview.expose() bridge, which lets you declare a namespace of values and async functions on the page’s global scope and call them like ordinary JavaScript. Both mechanisms run on the same thread without an HTTP server.

Sending messages from the page

wry automatically injects window.ipc.postMessage() into every page. Register your Node-side handler with webview.onIpcMessage(), then trigger postMessage from any page-side event.
message.body is a Buffer. Call .toString('utf8') (or another encoding) to read it as a string.

Custom IPC channel name

By default the only global available is window.ipc. Pass ipcName when creating the webview to add an alias for your own namespace. Both names call the same handler.
The page can now call window.bindings.postMessage(...) or window.ipc.postMessage(...) interchangeably.
On Windows, window.ipc.postMessage() does not fire when the page is loaded from a file: URL. Load IPC-enabled pages through a custom protocol such as app:// instead. See Custom Protocols.

Sending data from Node to page

Use evaluateScript() for one-way updates — DOM manipulation, setting a title, or injecting data.
Use evaluateScriptWithCallback() when you need the evaluated result back in Node. The callback receives a serialized string result (or an error).

JSON messages

IPC message bodies are raw bytes. Using JSON is a practical convention for structured data without building a custom binary protocol.
If you find yourself building a request/response protocol on top of raw IPC, consider using webview.expose() instead — it handles serialization, routing, and Promise resolution for you.

The expose() bridge

webview.expose(name, target) is the higher-level alternative to raw IPC. It registers a namespace on window in the page. Static JSON-serializable values appear directly on the namespace; async functions are wrapped so that every call from the page returns a Promise. Node side — expose a namespace with static values and async functions:
Page side — access the namespace immediately for static values and await functions:

expose() rules and limits

What you can expose:
  • JSON-serializable scalar values: strings, numbers, booleans, null, plain objects, arrays
  • async functions (or functions that return a Promise)
What you cannot expose: Namespace uniqueness: Each namespace name can only be exposed once per Webview instance. Calling expose() with a name that is already registered throws immediately.
When serialization fails — for example because you return undefined from an exposed function or pass a BigInt as an argument — WebviewJS throws a SerializationError. Catch it in your async function or handle it in a .catch() on the Promise returned by the page.

  • Custom Protocols — serve local files over app:// (required for IPC on Windows)
  • Webview API reference — full method signatures for onIpcMessage, evaluateScript, evaluateScriptWithCallback, and expose