This project is in beta — APIs may change without notice.
@nativewindow/webview

NativeWindow

API reference for the native window addon

NativeWindow Class

The NativeWindow class creates a native OS window with an embedded webview. It wraps the Rust napi-rs addon (powered by wry + tao) and manages the lifecycle automatically:

  • Auto-init — the native subsystem initializes on first window creation
  • Auto-pump — events are pumped at ~60fps (16ms interval) automatically
  • Auto-stop — the event pump stops when all windows are closed
import { NativeWindow } from "@nativewindow/webview";

const win = new NativeWindow({
  title: "My App",
  width: 1024,
  height: 768,
  devtools: true,
});

Properties

id (readonly)

Unique numeric identifier for the window, assigned at construction time.

const win = new NativeWindow({ title: "My App" });
console.log(win.id); // e.g. 1

WindowOptions

All options are optional. Pass them to the NativeWindow constructor:

OptionTypeDefaultDescription
titlestring""Window title
widthnumber800Inner width in logical pixels
heightnumber600Inner height in logical pixels
xnumberX position in screen coordinates
ynumberY position in screen coordinates
minWidthnumberMinimum inner width
minHeightnumberMinimum inner height
maxWidthnumberMaximum inner width
maxHeightnumberMaximum inner height
resizablebooleantrueAllow window resizing
decorationsbooleantrueShow title bar and borders
transparentbooleanfalseTransparent window background
alwaysOnTopbooleanfalseFloat above other windows
visiblebooleantrueShow window immediately on creation
devtoolsbooleanfalseEnable browser devtools
cspstringContent Security Policy injected at document start via a <meta> tag
trustedOriginsstring[]Native-layer IPC origin filter; messages from non-matching origins are silently dropped (defense-in-depth — see also trustedOrigins in Typed IPC)
allowedHostsstring[]Restrict all navigations to matching hosts. Supports wildcard prefixes ("*.example.com" matches subdomains and the base domain). Internal URLs (about:blank, loadHtml() content) are always permitted. See Security guide
allowCamerabooleanfalseAllow the webview to access the camera. All camera requests are denied by default. See Security guide
allowMicrophonebooleanfalseAllow the webview to access the microphone. All microphone requests are denied by default. See Security guide
allowFileSystembooleanfalseAllow the webview to use the File System Access API (showOpenFilePicker, showSaveFilePicker, showDirectoryPicker). Windows only — WebKit does not support this API on macOS or Linux. See Security guide
iconstringPath to a PNG or ICO file for the window icon (title bar). On macOS this is silently ignored (macOS doesn't support per-window icons). Relative paths resolve from the working directory
incognitobooleanfalseRun the webview in incognito (private) mode. No cookies, cache, or browsing data are persisted to disk. Each window starts with a clean, isolated session and all data is discarded when the window closes. See Incognito Mode for platform details

Content Loading

loadUrl(url: string): void

Navigate the webview to a URL.

win.loadUrl("https://example.com");

Security: javascript:, file:, data:, and blob: URL schemes are blocked and will throw an error. Only http: and https: URLs are allowed. See the Security guide for details.

loadHtml(html: string): void

Load an HTML string directly into the webview.

win.loadHtml("<h1>Hello</h1>");

Security: Never interpolate unsanitized user input into HTML strings. Use a sanitization library such as DOMPurify or sanitize-html. See the Security guide for details.

evaluateJs(script: string): void

Execute JavaScript in the webview context. This is fire-and-forget — there is no return value. Use postMessage/onMessage to send results back.

win.evaluateJs('document.title = "Updated"');

Security: Never pass unsanitized user input directly. Use sanitizeForJs() to escape strings. See the Security guide for details.

postMessage(message: string): void

Send a string message to the webview. The message is delivered via the window.__native_message__ callback on the webview side.

win.postMessage("data from host");

Window Control

MethodDescription
setTitle(title: string)Set the window title
setSize(width: number, height: number)Set the window size in logical pixels
setMinSize(width: number, height: number)Set minimum window size
setMaxSize(width: number, height: number)Set maximum window size
setPosition(x: number, y: number)Set window position in screen coordinates
setResizable(resizable: boolean)Enable or disable window resizing
setDecorations(decorations: boolean)Show or hide title bar and borders
setAlwaysOnTop(alwaysOnTop: boolean)Toggle always-on-top mode
setIcon(path: string)Set the window icon from a PNG or ICO file path. Silently ignored on macOS

Window State

MethodDescription
show()Show the window
hide()Hide the window
close()Close and destroy the window
focus()Bring the window to focus
maximize()Maximize the window
minimize()Minimize the window
unmaximize()Restore the window from maximized state
reload()Reload the current page in the webview

Note: All public methods throw Error("Window is closed") if called after close(). The NativeWindow tracks its closed state internally and rejects further operations.

Events

MethodCallback Signature
onMessage(cb)(message: string, sourceUrl: string) => void
onClose(cb)() => void
onResize(cb)(width: number, height: number) => void
onMove(cb)(x: number, y: number) => void
onFocus(cb)() => void
onBlur(cb)() => void
onPageLoad(cb)(event: "started" | "finished", url: string) => void
onTitleChanged(cb)(title: string) => void
onReload(cb)() => void
onNavigationBlocked(cb)(url: string) => void

Example:

win.onPageLoad((event, url) => {
  if (event === "finished") {
    console.log("Page loaded:", url);
  }
});

win.onResize((width, height) => {
  console.log(`Window resized to ${width}x${height}`);
});

Security: The raw onMessage callback does not filter by origin — all messages from the webview are delivered regardless of the source page URL. Use the sourceUrl parameter to validate the origin yourself, or use Typed IPC with trustedOrigins for automatic origin filtering.

Note: Calling onClose() more than once replaces the previous handler and emits a console.warn. Use a single handler with all your cleanup logic.

onNavigationBlocked

Fired when a navigation is blocked by the allowedHosts restriction. Use it to log blocked attempts or notify the user:

const win = new NativeWindow({
  title: "My App",
  allowedHosts: ["myapp.com", "*.cdn.myapp.com"],
});

win.onNavigationBlocked((url) => {
  console.warn("Blocked navigation to:", url);
});

getCookies(url?: string): CookieInfo[]

Query cookies from the native cookie store. Returns validated CookieInfo objects synchronously, including HttpOnly cookies that are invisible to document.cookie.

const cookies = win.getCookies("https://example.com");
const session = cookies.find((c) => c.name === "session_id");
if (session) {
  console.log("Session:", session.value, "HttpOnly:", session.httpOnly);
}

If url is provided, only cookies matching that URL are returned. If omitted, all cookies in the webview's cookie store are returned.

Platform behavior:

  • Cookie access uses wry's cross-platform cookie API under the hood
  • If url is provided, only cookies matching that URL are returned on all platforms

Security: getCookies() returns HttpOnly cookies that are inaccessible to document.cookie in the webview. Treat the returned data as sensitive — do not expose cookie values to untrusted contexts.

CookieInfo

Return type for each cookie from getCookies():

interface CookieInfo {
  name: string; // Cookie name
  value: string; // Cookie value
  domain: string; // Domain the cookie belongs to
  path: string; // Path the cookie is restricted to
  httpOnly: boolean; // Whether the cookie is HttpOnly (inaccessible to JS)
  secure: boolean; // Whether the cookie requires HTTPS
  sameSite: string | null; // SameSite policy: "Strict", "Lax", "None", or null
  expires: number | null; // Expiry as Unix timestamp (seconds); null for session cookies
}

clearCookies(host?: string): void

Clear cookies from the native cookie store. If host is provided, only cookies whose domain matches that host are deleted. If omitted, all cookies in the webview's cookie store are cleared.

// Clear all cookies
win.clearCookies();

// Clear cookies for a specific host
win.clearCookies("example.com");

DevTools

openDevtools(): void

Open the browser devtools panel for this window's webview. Requires devtools: true in WindowOptions.

const win = new NativeWindow({ title: "Debug", devtools: true });
win.loadUrl("https://example.com");
win.openDevtools();

closeDevtools(): void

Close the browser devtools panel for this window's webview.

win.closeDevtools();

isDevtoolsOpen(): boolean

Check whether the devtools panel is currently open.

if (win.isDevtoolsOpen()) {
  console.log("DevTools are open");
}

Security: DevTools grant full DOM/JS inspection access. Only enable devtools: true during development; avoid enabling it in production to reduce the attack surface.

Incognito Mode

Set incognito: true to run the webview in private/incognito mode. No cookies, cache, or other browsing data are persisted to disk. Each window starts with a clean, isolated session and all data is discarded when the window closes.

const win = new NativeWindow({
  title: "Private Session",
  incognito: true,
});

Platform behavior:

PlatformImplementation
macOSUses WKWebsiteDataStore.nonPersistentDataStore() (WKWebView)
WindowsEnables IsInPrivateModeEnabled on the WebView2 controller
LinuxUses a temporary in-memory WebContext (no persistent storage)

Tip: Combine incognito: true with security features like csp and trustedOrigins for maximum isolation when loading untrusted content.

Utility Functions

sanitizeForJs

Escape a string for safe embedding inside a JavaScript string literal. Handles backslashes, quotes, backticks, template expressions (${}), newlines, null bytes, closing </script> tags, and Unicode line/paragraph separators.

import { NativeWindow, sanitizeForJs } from "@nativewindow/webview";

const userInput = 'He said "hello"\n<script>alert(1)</script>';
win.evaluateJs(`display("${sanitizeForJs(userInput)}")`);

checkRuntime(): RuntimeInfo

Check if the native webview runtime is available. Returns { available: boolean, version?: string, platform: "macos" | "windows" | "linux" | "unsupported" }.

ensureRuntime(): RuntimeInfo

Check for the runtime and install it if missing (Windows only). Downloads the WebView2 Evergreen Bootstrapper (~2MB) from Microsoft and runs it silently. The downloaded executable is verified using Authenticode signature validation before execution — if verification fails, the bootstrapper is not executed and the function throws. Throws on failure.

Security: Do not call ensureRuntime() in an elevated (Administrator) context without explicit user consent — the silent installer applies system-wide. Prefer calling checkRuntime() first to avoid unnecessary network requests when the runtime is already present.

RuntimeInfo

Return type for checkRuntime() and ensureRuntime():

interface RuntimeInfo {
  available: boolean;
  version?: string;
  platform: "macos" | "windows" | "linux" | "unsupported";
}

Legacy API

The following functions exist for backward compatibility but are not needed when using the NativeWindow class, which manages initialization and event pumping automatically:

FunctionDescription
init()Initialize the native window system manually
pumpEvents()Process pending native UI events manually

Known Limitations

  • ~16ms event latency from the pumpEvents() polling interval
  • HTML origin differences — content loaded via loadHtml() has a https://nativewindow.localhost origin on Windows and a nativewindow://localhost origin on macOS and Linux (both are secure contexts). When configuring trustedOrigins, use the platform-appropriate value.
  • No return values from unsafe.evaluateJs() — use postMessage/onMessage to send results back
  • 2 MB HTML limit on Windows when using loadHtml()
  • 10 MB message size limit — messages larger than 10 MB are silently dropped at the native layer
  • javascript:, file:, data:, and blob: URLs are blockedloadUrl() and in-page navigations reject these schemes
  • Use bun --watch (or equivalent) instead of bun --hot for development (native addon reloading requires a process restart)

On this page