Beyond one window: plugins, native surfaces, and mobile constraints

This chapter examines how Tauri extends its boundary through plugins, multiple windows, child webviews, and mobile adapters. The focus is the hidden coupling created when more code and more UI surfaces share one Rust process: a plugin can add native behavior and JavaScript APIs, while a new webview can add a different origin and authority context. Understanding those relationships lets you design extensions and multi-surface applications without turning portability into an excuse for one unrestricted bridge.

A plugin is a boundary-bearing subsystem

A Tauri plugin is easiest to misunderstand when it is treated as a package of convenience functions. In the repository, the plugin interface is closer to a small application subsystem. It has an identity, initialization, configuration, state and setup opportunities, JavaScript initialization, lifecycle hooks, command dispatch, optional URI protocols, and cleanup. Each part attaches to a different point in the host application's boundary.

Initialization is the first attachment. A plugin has a name that is used in configuration and command routing. When the host initializes it, Tauri supplies an application handle and the plugin's configuration value. A builder-created plugin can deserialize that value into a typed configuration object, run setup, and register its URI protocols with the webview manager. The plugin can therefore establish native resources and managed state before its JavaScript surface begins making requests.

The plugin's setup code owns the meaning of that state. It might create a platform service, register a database handle, or prepare a native integration. Tauri supplies the manager and the lifecycle point; it does not decide whether the plugin should hold a global cache, how it should synchronize access, or which failures should be visible to the frontend. The same ownership rule that applies to application commands applies inside an extension.

JavaScript initialization is a second attachment. A plugin can provide a script that runs after the Tauri global object has been created but before the document's ordinary scripts execute. That allows a plugin to expose a small JavaScript surface or install the glue needed by its API. The script is injected into the webview during preparation, and the plugin interface distinguishes scripts for the main frame from scripts that should also run in child frames.

This timing is powerful and easy to overuse. A script that runs in every frame can encounter pages the plugin did not intend to modify, so the repository recommends guarding such code with a location check. On platforms where the underlying webview has weaker document-start support, initialization timing can differ for remote URLs. A plugin's JavaScript contract must therefore state which origins and frames it expects, rather than assuming that “injected” means “identical everywhere.”

The lifecycle hooks connect the plugin to the native application around the bridge. A plugin can observe a window or webview being created, decide whether navigation should proceed, respond to page-load events, receive event-loop events, and run cleanup when the plugin is dropped. These hooks make the plugin aware of the host's temporal state. They also create failure surfaces: a navigation policy can cancel a page, a page-load hook can run before an expected resource is available, and cleanup can race with work that still holds an application handle.

The command side follows a named route. Plugin commands use a namespace such as plugin:name|operation, which lets Tauri distinguish them from application commands. The webview request path parses that namespace, checks contextual authority, and only then asks the plugin store to handle the operation.