# How to Create a Separate Settings Window in a Tauri Application

> Learn to create a separate settings window in a Tauri application. Define unique labels, check for existing windows, and build a new one using Rust commands and the JS API.

- Repository: [lencx/ChatGPT](https://github.com/lencx/ChatGPT)
- Tags: how-to-guide
- Published: 2026-03-06

---

**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`](https://github.com/lencx/ChatGPT/blob/main/src-tauri/src/core/constant.rs), the repository declares a static string for the settings window identifier:

```rust
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`](https://github.com/lencx/ChatGPT/blob/main/src-tauri/src/core/window.rs), the `open_settings` function implements this check:

```rust
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`](https://github.com/lencx/ChatGPT/blob/main/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`](https://github.com/lencx/ChatGPT/blob/main/src-tauri/src/main.rs), add `open_settings` to the invoke handler:

```rust
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.

In [`src/view/Titlebar.tsx`](https://github.com/lencx/ChatGPT/blob/main/src/view/Titlebar.tsx):

```tsx
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`](https://github.com/lencx/ChatGPT/blob/main/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`](https://github.com/lencx/ChatGPT/blob/main/src/view/Settings.tsx) might look like:

```tsx
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 calling `WebviewWindowBuilder::new()` to prevent duplicate windows and properly handle focus.
- **Shared entry point**: Loading [`index.html`](https://github.com/lencx/ChatGPT/blob/main/index.html) in 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`](https://github.com/lencx/ChatGPT/blob/main/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`](https://github.com/lencx/ChatGPT/blob/main/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`](https://github.com/lencx/ChatGPT/blob/main/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`](https://github.com/lencx/ChatGPT/blob/main/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.