How to Create a Separate Settings Window in a Tauri Application
To create a separate settings window in a Tauri application, define a unique window label constant in Rust, expose a command that checks for an existing window before creating a new one with WebviewWindowBuilder, and invoke that command from your frontend using Tauri's JavaScript API.
Implementing a dedicated settings window requires coordination between Rust's native window management and your frontend framework. According to the lencx/ChatGPT source code, the implementation relies on label-based window tracking to ensure only one settings instance exists while allowing the same SPA entry point to power multiple windows.
Define the Window Label Constant
Tauri identifies each webview window by a unique string label. Centralizing this identifier prevents typos and ensures consistent reference across your Rust codebase.
In src-tauri/src/core/constant.rs, the repository declares a static string for the settings window identifier:
pub static WINDOW_SETTINGS: &str = "settings";
This constant is imported by window management code to consistently reference the settings view.
Create the Rust Window Command
The core logic resides in a Tauri command that either reveals an existing settings window or constructs a new one. This pattern prevents duplicate windows and handles window lifecycle management on the Rust side.
In src-tauri/src/core/window.rs, the open_settings function implements this check:
use tauri::{command, AppHandle, Manager, WebviewUrl, WebviewWindowBuilder};
use crate::core::constant::WINDOW_SETTINGS;
#[command]
pub fn open_settings(app: AppHandle) {
match app.get_webview_window(WINDOW_SETTINGS) {
Some(window) => {
// Window exists - bring it to foreground
window.show().unwrap();
}
None => {
// Create new window loading the main HTML entry
WebviewWindowBuilder::new(
&app,
WINDOW_SETTINGS,
WebviewUrl::App("index.html".into()),
)
.build()
.unwrap();
}
}
}
The function uses AppHandle::get_webview_window to query for an existing window using the label constant. If found, it calls show() to focus the window. Otherwise, it uses WebviewWindowBuilder to instantiate a new native window that loads index.html, allowing your frontend router or conditional rendering to display the settings interface.
Register the Command in Main
Expose the command to the frontend by registering it in your application builder. In src-tauri/src/main.rs, add open_settings to the invoke handler:
fn main() {
tauri::Builder::default()
.invoke_handler(tauri::generate_handler![open_settings])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
Trigger the Window from the Frontend
Invoke the Rust command from your UI using @tauri-apps/api/core. The ChatGPT app triggers this via a settings icon in the title bar component.
import { invoke } from '@tauri-apps/api/core';
import SettingIcon from '~icons/Setting';
const handleSetting = () => {
invoke('open_settings');
};
// In your JSX:
<SettingIcon action onClick={handleSetting} />
When the user clicks the settings icon, the frontend calls invoke('open_settings'), which executes the Rust command to display or create the window.
Render Settings Content Conditionally
Because the new window loads index.html (the same entry point as your main window), you handle settings-specific rendering on the frontend. Typically, you detect the window context via URL routing or query parameters.
A simple settings component in src/view/Settings.tsx might look like:
export default function Settings() {
return (
<div className="settings-container">
<h1>Application Settings</h1>
{/* Settings controls here */}
</div>
);
}
Your router can render this component when the window navigates to a route like /settings, or you can check window.location to determine which view to display.
Summary
- Label-based management: Using a constant string identifier (
WINDOW_SETTINGS) ensures Tauri tracks the settings window as a singleton. - Show-or-create pattern: Check
app.get_webview_window()before callingWebviewWindowBuilder::new()to prevent duplicate windows and properly handle focus. - Shared entry point: Loading
index.htmlin secondary windows keeps binary sizes small while allowing frontend routing to control the displayed content. - IPC invocation: Frontend code triggers native window operations via
invoke()from@tauri-apps/api/core.
Frequently Asked Questions
How do I prevent users from opening multiple settings windows?
The implementation in src-tauri/src/core/window.rs prevents duplicates by checking app.get_webview_window(WINDOW_SETTINGS) before creating a new window. If a window with that label exists, the code calls .show() instead of constructing a new instance, ensuring only one settings window is ever active.
Can I load a separate HTML file instead of index.html for the settings window?
Yes. Change the WebviewUrl::App parameter in WebviewWindowBuilder::new() from "index.html" to another file path like "settings.html". However, using the shared index.html entry point with client-side routing (as implemented in lencx/ChatGPT) reduces build complexity and maintains consistent styling through shared CSS bundles.
How do I pass data between the main window and the settings window?
Use Tauri's event system with emit and listen from @tauri-apps/api/event, or create additional Rust commands that both windows can invoke. Since both windows run the same application context, they share access to the Tauri command layer and can communicate through the Rust backend or via localStorage if using a shared origin.
Why does the settings window use the same SPA entry point as the main window?
Loading index.html in both windows allows the application to leverage code splitting and shared component libraries without duplicating assets in the final binary. The settings view renders as a conditional component based on the route or window label, minimizing memory footprint and ensuring consistent application state management across windows.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →