Before the window opens: how configuration becomes a running app
For Tauri, the path to a native window begins before the application process starts. This chapter traces how configuration and frontend files move through build hooks, ACL preparation, asset code generation, runtime construction, webview creation, setup, and the event loop. That lifecycle matters because it tells you which facts are fixed in the binary, which remain external in development, and where a web/native boundary is created or changed.
The build is a state transition, not a prelude
A developer often experiences a Tauri build as a command that eventually produces an executable. The repository reveals a more consequential transition: project inputs become generated program data. Configuration is not merely read by a launcher at runtime. The build integration parses it, validates it, derives platform-related settings, prepares access-control data, and makes information available to code generation and Cargo.
The build script is part of this path. Tauri's build helpers document that a build script is required to activate environment variables used for code generation and asset embedding. During that work, the helper reads the target-specific configuration, establishes desktop or mobile compilation aliases, records files that should trigger a rebuild, and builds the ACL material associated with the application and its plugins. It can also copy configured resources and platform-specific dependencies into the build output.
The access-control step is important even before any command is invoked. Application and plugin permission definitions are gathered into manifests. Capability files are parsed and checked against the permissions that actually exist. The build produces a representation of capabilities and allowed commands that later code generation can consume. This means a typo or unknown permission can fail while building rather than becoming a silent runtime interpretation. It also means the eventual runtime does not need to reconstruct the entire project directory to understand its resolved authority.
The frontend takes one of two broad paths. In development, the CLI can run a frontend command and wait for a development server described by the configuration. The page may remain at a development URL, where a frontend tool provides hot reloading and serves the latest files. In a production-style build, the CLI validates the configured frontend distribution and the code-generation path can walk the selected directory or file list, transform the inputs, and place the resulting assets in generated application context.
These paths are not interchangeable. A development server is an external process with its own URL, availability, certificates, reload behavior, and dependency graph. An embedded production distribution is part of the application binary's asset provider. A frontend change can be visible immediately in development without recompiling the Rust application, while a change to embedded assets becomes a build input. The difference also affects origin handling: the page loaded from a development server is not necessarily the same origin as the page served by Tauri's application protocol.
Tauri's code-generation layer makes the production path concrete. It selects a development URL when the configuration and build mode call for one; otherwise it reads a frontend directory or an explicit set of files. The asset collector walks files, normalizes their keys, applies configured transformations, and computes content hashes for generated output. With the default compression feature, the stored representation is compressed and the runtime asset provider decompresses it when a matching asset is requested. The generated code includes the resulting bytes through compiler-managed inclusions rather than asking the finished application to search the original frontend project.
That design has two effects that are easy to miss. First, embedding is selective: the configured frontend distribution is not automatically the same thing as the entire repository. Second, the generated asset map is a runtime service, not just a pile of bytes. It can return an asset, iterate over the embedded set, and provide CSP hash information associated with global scripts or individual HTML files. The application protocol can then use that information when it serves a page.
Security metadata is prepared in the same transition. If a CSP is configured, Tauri's HTML processing can inject nonce tokens into applicable scripts and styles, normalize scripts for hashing, and record hashes for scripts and inline content. The configuration can also request that Object.prototype be frozen in the custom-protocol path. These controls are not guarantees that an application has a perfect content policy. They show instead that the build pipeline treats the webview document as an input whose execution policy can be changed before it reaches the user.
The integrated API example makes this build-time composition visible without requiring a reader to inspect every internal file. Its configuration combines a development URL and frontend distribution, a build hook, unused-command removal, an isolation pattern, CSP settings, prototype freezing, and an asset protocol scope. Its build script adds application and inlined-plugin command information to ACL generation. The example is not a universal default, but it demonstrates that code generation, policy, and frontend assets are designed to meet at one boundary.
A useful way to summarize the build transition is this: the project supplies declarations and files; the build system converts them into a target-specific context. Some source information is intentionally left outside that context, such as a development server's live content. Other information becomes fixed or derived, such as embedded asset bytes, CSP hashes, capability resolution, icons, and the application pattern. Knowing which side of that divide a fact occupies is essential when debugging a release.
From generated context to a live event loop
Once the build has produced the context, the application still has to become a running native system. The generated Context carries more than a URL. It contains configuration, an asset provider, icons, package information, the application pattern, resolved runtime authority, and optional plugin API scripts. Tauri's Builder adds the remaining behavior supplied by application code: command handling, setup, plugins, custom protocols, managed state, event listeners, and the initialization script that establishes the JavaScript bridge.
The runtime lifecycle can be represented as a sequence of explicit states:
The first runtime state is not yet a visible application. Builder::build creates an application manager around the generated context. That manager owns the runtime authority, window and webview managers, plugin store, shared application state, assets, pattern, resource table, channel support, and the runtime-generated invoke key. Tauri then initializes the selected runtime, registers its core plugins, manages the environment and scopes needed by built-in APIs, and initializes user plugins.
This is where the architecture's ownership map becomes operational. The manager owns the relationships between windows, webviews, plugins, state, assets, and policy. The runtime owns the native event loop and provides a handle for dispatching work. Application code owns its commands, state values, and setup decisions. None of those roles is complete in isolation. A command can be registered before a window exists; a window can be created before a page finishes loading; a plugin can contribute an initialization script before its JavaScript API is called. The lifecycle determines when each relationship becomes usable.
At the runtime's Ready event, Tauri performs the setup transition. Configured windows whose creation flag is enabled are built from their window configuration. Each pending webview is then prepared with the scripts and handlers that apply to it. The preparation can include the internal Tauri objects, the IPC initialization, metadata for the current window and webview, plugin initialization scripts, pattern-specific code, the tauri asset protocol, the ipc protocol, and an optional asset protocol. Page-load and permission hooks are connected at this point as well.
Only after those configured windows have been created does Tauri invoke the application's setup hook. This order gives setup code a way to retrieve and configure the initial webview window, register additional state, create another window, or initialize product-specific services. It also creates a meaningful failure boundary. A malformed generated context or unavailable runtime can prevent the application from reaching setup. A setup error occurs after the runtime and core pieces exist but before the application is considered ready. A page-load error belongs to a later webview transition.
The API example uses this lifecycle in a visible way. Its setup function initializes plugins, creates a webview window, configures document-title and new-window behavior, invokes a sample plugin, and opens developer tools in debug builds. Its page-load hook registers a JavaScript event listener after a page finishes loading, then emits a Rust-side event in response. Its run callback can prevent an ordinary exit when all windows close so that a tray interface continues to operate. These are not separate launch mechanisms; they are hooks attached to different points in the same lifecycle.
The event loop remains the owner of native UI progress. Tauri's runtime interface says that runtime creation must happen on the main thread, while runtime handles and event-loop proxies let other code request work. The distinction is practical. A worker can obtain a window handle and ask the runtime to change its title or bounds, but the actual native operation must be delivered in a way that respects the backend's thread rules. A getter that needs a native answer may send a message and wait for a response. A long-running command should not block the thread that the webview needs to process events.
This ownership model also explains why a handle is not the same as the runtime itself. The runtime holds the event loop and native objects in the context where they can be safely manipulated. A handle is a dispatch capability. It lets application code and plugins communicate with the runtime without taking over its loop. That separation supports background work, but it does not remove the need to choose the right execution context or to account for shutdown.
Exit is another transition rather than a boolean flag. When the runtime reports an exit, Tauri translates the event, invokes the application's event callback, performs cleanup, and checks whether a restart was requested. A normal exit terminates the lifecycle. A restart path begins a new process lifecycle, which means generated context and initialization scripts are constructed again rather than being carried as live runtime state. Managed state, in-memory callbacks, and webview objects therefore do not survive a restart merely because the application requested one.
The difference between build-time and runtime state is especially important for configuration. The configuration file is an input to code generation and CLI orchestration; the running application receives a generated configuration object and associated assets. A development URL may still point to a process outside Tauri, while a production application protocol resolves an embedded asset through the manager. Permission definitions may be resolved into runtime authority maps, while the command implementation remains responsible for using scoped data correctly. A runtime object is the result of these transformations, not a transparent view of the source project.
The CLI makes the transitions easier to invoke but does not make them disappear. tauri dev can run a frontend command, start a built-in static server when appropriate, wait for a configured URL, and restart the Rust process when watched files change. tauri build can run a frontend build hook, verify that the configured distribution exists, compile the application, and hand the result to the bundler. The source still has to provide the Rust and frontend toolchains, the platform webview runtime, and any target-specific system dependencies.
This matters for reproducibility and diagnosis. If the frontend does not appear, ask whether the development server started, whether the configured distribution exists, and whether the webview is loading an external or embedded URL. If a permission is absent, ask whether the build generated the expected manifest and capability set. If a plugin's script is missing, ask when the plugin was initialized relative to webview creation. If a window call hangs, ask which thread owns the runtime and whether the operation was dispatched correctly. Each question follows the lifecycle rather than treating “the app” as one undifferentiated state.
The lifecycle ends with a useful boundary statement. A Tauri executable is not simply a frontend bundle plus a window. Before the first page is visible, configuration has selected operating conditions, the build has derived authority and assets, code generation has prepared the context, the runtime has created native managers, and the webview has received the bridge and policies that make later requests possible. The next test is whether you can classify a concrete behavior correctly: is it fixed in the generated context, created by the runtime manager, supplied by the webview, owned by Rust application code, or delegated to the operating system? That classification is the foundation for tracing the live bridge.