Please check the build logs for more information.
See Builds for ideas on how to fix a failed build, or Metadata for how to configure docs.rs builds.
If you believe this is docs.rs' fault, open an issue.
Open Tauri Remote WebView
Backend (Rust library): cargo add open-tauri-remote-webview (run in src-tauri/)
Frontend (JS): npm install open-tauri-remote-webview (run in project root)
中文用户请查看 中文文档 — 包含完整的使用指引。
Why this plugin?
Building a Tauri app? Some things are simply impossible with native Tauri alone:
"I want E2E tests with Playwright or Cypress, but they can't see Tauri's WebView." Playwright and Cypress control standard browsers. Tauri apps run inside the system WebView — invisible to these tools. This plugin exposes your app's UI through a standard WebSocket, so any web testing tool works out of the box.
"My app needs to run on a headless server, but there's no display." Native Tauri requires a local display environment. You can't start it on a server and access the UI remotely. This plugin makes your app work like a web service — start it on the server, open it in any browser. No VNC, no remote desktop needed.
"CI/CD UI testing is a nightmare — xvfb, display mocking, fragile setup." Testing native Tauri in CI requires virtual display setup and still can't integrate with web testing frameworks. This plugin lets your CI pipeline connect Playwright directly to your app, as simple as testing a regular website.
"Every developer on the team needs the full Rust + Tauri toolchain just to work on the frontend." With this plugin, team members open a browser to see the app UI. No Rust installation, no Tauri environment setup. Frontend developers can work independently.
Long story short:
One import line, and your Tauri app works in a browser — none of the above is possible with native Tauri, and you don't change a single line of business logic.
This project is based on tauri-remote-ui v0.14.0
by DraviaVemal, modified under the MIT license.
All modifications are Copyright (c) 2026 ant-cave. (MIT — use it, modify it, sell it. Just keep the copyright notice if you redistribute.)
Features
| Capability | WebView (IPC) | Browser (WS) |
|---|---|---|
invoke (any command) |
✅ | ✅ |
Event listen / once |
✅ | ✅ |
@tauri-apps/api/app (getName, getVersion, ...) |
✅ | ✅ via bridge |
@tauri-apps/api/window (title, size, ...) |
✅ | ✅ via bridge |
@tauri-apps/api/event (listen, emit) |
✅ | ✅ via bridge |
Rust emit / emit_to / emit_str / ... → browser |
✅ | ✅ via EmitterExt |
| Custom security & origin control | ✅ | ✅ |
| WS connection status floating badge | — | ✅ auto-shown |
API Compatibility with Native Tauri
Zero migration cost. All exported APIs have identical signatures to native Tauri:
Rust side
Native tauri::Emitter |
This plugin open_tauri_remote_webview::EmitterExt |
Difference |
|---|---|---|
fn emit(...) sync |
fn emit(...) sync ✅ |
None |
fn emit_to(...) sync |
fn emit_to(...) sync ✅ |
None |
fn emit_str(...) sync |
fn emit_str(...) sync ✅ |
None |
fn emit_str_to(...) sync |
fn emit_str_to(...) sync ✅ |
None |
fn emit_filter(...) sync |
fn emit_filter(...) sync ✅ |
None |
fn emit_str_filter(...) sync |
fn emit_str_filter(...) sync ✅ |
None |
Usage: Simply replace use tauri::Emitter with use open_tauri_remote_webview::EmitterExt — all calls continue to work unchanged.
JS side
Native @tauri-apps/api |
Bridge mode | Explicit API | Difference |
|---|---|---|---|
invoke<T>(cmd, args): Promise<T> |
Transparent proxy ✅ | invoke<T>(cmd, args) ✅ |
None |
listen<T>(event, handler): Promise<() => void> |
Transparent proxy ✅ | listen<T>(event, handler) ✅ |
None |
once<T>(event, handler): Promise<() => void> |
Transparent proxy ✅ | once<T>(event, handler) ✅ |
None |
Known limitations (architectural, not API differences)
- Single-window mode only:
getCurrentWindow()always returnslabel: "main" - No asset protocol:
convertFileSrc()returns the path as-is; use raw URLs for assets - No
__TAURI__env: the bridge sets__TAURI_REMOTE_UI_SHIM__instead
Migration from Native Tauri
Migrating an existing Tauri app requires only a few changes. Most of your frontend code stays exactly the same.
What changes
| Area | Before (native Tauri) | After (remote) |
|---|---|---|
| Rust plugin | — | Add crate + .plugin(open_tauri_remote_webview::init()) |
Rust emit() (AppHandle / WebviewWindow) |
use tauri::Emitter |
use open_tauri_remote_webview::EmitterExt (same call, sync) |
| Rust start WS server | — | Add app.start_remote_ui(RemoteUiConfig::default()) in setup |
| Frontend install | — | npm install open-tauri-remote-webview |
| Frontend import | import "..." from "@tauri-apps/api" |
No change — add import "open-tauri-remote-webview/bridge-init" at entry |
vite.config.ts |
— | Add /remote_ui_ws proxy |
if (isTauri()) guards |
Common pattern | Remove them — bridge handles everything |
Step-by-step
- Rust:
cargo add open-tauri-remote-webview, register the plugin and start the WS server insetup(see Usage > Rust side). - Frontend:
npm install open-tauri-remote-webview, addimport "open-tauri-remote-webview/bridge-init"at the top of your entry file. - Vite: Add the
/remote_ui_wsproxy (see Usage > Vite dev proxy). - Clean up: Delete all
isTauri()/isRunningInTauri()branches — the bridge handles environment detection automatically. - Recommended: Replace
use tauri::Emitterwithuse open_tauri_remote_webview::EmitterExtso backend events also reach browser clients.
What stays the same
- All
@tauri-apps/api/*imports and usage - All Tauri command definitions on the Rust side
- All frontend build tooling (Vite, Webpack, etc.)
- All event
listen/once/emitpatterns - All window and app API calls
Usage
1. Rust side
Optional: The
wsCargo feature (enabled by default) controls WebSocket support. Disable it with--no-default-featuresif you only need IPC in WebView and want to drop the WebSocket dependencies.
use ;
default
.plugin
.setup
.invoke_handler
.run
.expect;
If you use emit() / emit_to() / emit_str() / etc. in Rust, replace
use tauri::Emitter with use open_tauri_remote_webview::EmitterExt so events
also get forwarded to browser clients. EmitterExt is implemented for
AppHandle and WebviewWindow with the same synchronous signatures as
tauri::Emitter — it is a true drop-in replacement.
2. Frontend — zero-effort (recommended)
In your app entry point (main.ts / main.js), add one line at the top:
// Auto-inject __TAURI_INTERNALS__ WS proxy — @tauri-apps/api works everywhere
import "open-tauri-remote-webview/bridge-init";
// Keep using @tauri-apps/api as-is — no import changes needed
import from "@tauri-apps/api/core";
import from "@tauri-apps/api/app";
import from "@tauri-apps/api/window";
import from "@tauri-apps/api/event";
That's it. All @tauri-apps/api calls automatically go through IPC in
WebView and WebSocket in the browser — no if (isTauri()) branches needed.
bridge-init also auto-shows a WS connection status floating badge (draggable,
click to expand debug panel). To suppress it:
// Method 1 — set flag before import (globally disable)
window.__ORUI_DISABLE_BADGE__ = true;
import "open-tauri-remote-webview/bridge-init";
// Method 2 — call at runtime (toggle off)
import from "open-tauri-remote-webview/api/core";
;
By default the WS client auto-detects the server URL from window.location.
Override it with a global before import:
// Option A — full URL override
window.__ORUI_WS_URL__ = "ws://192.168.1.100:9090/remote_ui_ws";
// Option B — port override (uses current hostname + custom port)
window.__ORUI_WS_PORT__ = 9090;
import "open-tauri-remote-webview/bridge-init";
3. Frontend — explicit API (alternative)
import from "open-tauri-remote-webview/api/core";
import from "open-tauri-remote-webview/api/event";
4. Vite dev proxy
// vite.config.ts
server:
5. Start / Stop server manually
async
async
How the bridge works
Async environment detection
bridge-init asynchronously detects the runtime environment before taking
any action:
module load
↓
waitForTauriDetection() ← polls __TAURI_INTERNALS__ (up to 3s)
├── Native Tauri found → skip bridge, use real IPC
└── Browser detected → install WS bridge shim
This eliminates race conditions where __TAURI_INTERNALS__ isn't injected yet
when your frontend code runs. No isTauri() guards needed — the detection is
fully automatic.
WS proxy shim
The __TAURI_INTERNALS__ proxy shim (installTauriBridge) is injected when
your app runs in a browser. It mimics the real Tauri runtime:
Browser (@tauri-apps/api/*)
↓
window.__TAURI_INTERNALS__.invoke(cmd, args)
├── "plugin:event|listen" ──→ local WS event system (no round-trip)
├── "plugin:event|unlisten" ──→ remove local listener
└── everything else ──→ WS → Rust → WebView IPC → response
transformCallback/runCallback— managed locally inShimCallbackManagermetadata— hardcoded to{ label: "main" }(single-window mode)convertFileSrc— returns path as-is (browser has no asset protocol)plugins.path— inferred fromnavigator.platform__TAURI_REMOTE_UI_SHIM__flag — lets apps distinguish shim from real Tauri
Frontend structured logging
All modules use a built-in structured logger (src/logger.ts) with timestamps
and log levels (DEBUG / INFO / WARN / ERROR). Open browser DevTools to
see detailed lifecycle traces — useful for debugging connection issues and
understanding the bridge initialization flow.
WS Connection Status Floating Badge
bridge-init auto-shows a draggable connection status badge. Click to expand
the debug panel with latency, connect/reconnect count, uptime, WS URL, and logs.
import from "open-tauri-remote-webview/api/core";
; // show
; // hide
Features:
- Status display: connected / connecting / disconnected / error
- Draggable: move anywhere without blocking page content
- Debug panel: latency, connect count, reconnect count, uptime, WS address, error log
- Auto-reconnect: exponential backoff (1s → 2s → 4s → ... → 30s)
Package exports
| Import path | What it provides |
|---|---|
open-tauri-remote-webview/bridge-init |
Side-effect — auto-install bridge (recommended) |
open-tauri-remote-webview/api/bridge |
{ installTauriBridge } — manual install |
open-tauri-remote-webview/api/core |
{ invoke, setBaseUrl, getWsStatus, onWsStatusChange, getWsStats, initFloatingBadge, disableFloatingBadge } |
open-tauri-remote-webview/api/event |
{ listen, once } |
open-tauri-remote-webview |
Re-exports all of the above |
Plugin Development
# Build Rust
# Build JS
&&
# One shot: build JS → reinstall → launch test app (auto-watches guest-js for changes)
# Step by step:
# 1) Build JS
&&
# 2) Reinstall the local package in the test app
&& &&
# 3) Launch test app (with window + devtools)
&&
# Test app (headless, WS only — no window)
&&
cargo xtask devalso cleans ports 1420 and 9090 before starting, clears the Vite cache, and watchesguest-js/src/+guest-js/api/for changes — automatically rebuilding JS and hot-reinstalling when files change.
License
MIT — see LICENSE for the full text.
Based on tauri-remote-ui v0.14.0
Copyright (c) 2025 DraviaVemal, used under MIT.
Modifications and additions Copyright (c) 2026 ant-cave (antmmmmm@126.com / https://github.com/ant-cave).