Window Size & Position

How to set a Tauri window's initial size and position, constrain its resizing, move it programmatically, and restore its state across sessions using the window-state plugin.

The first thing any user sees is the window itself — how large it is, where it sits on the screen, and whether it remembers those choices the next time they launch the app. Tauri gives you control over all of these: you can set exact pixel dimensions, pin the window to a specific screen coordinate, center it, or allow it to be resized within bounds you define. This section covers the configuration options and the programmatic APIs that make that possible.

How Window Dimensions and Placement Are Defined

A Tauri window’s size is measured in logical pixels. The position uses a coordinate system where the origin is the top‑left corner of the primary monitor. Positive x moves the window right, positive y moves it down. Everything you set in the configuration file (tauri.conf.json) acts as the initial state when the window is created.

There are three layers you can work with:

  1. Static configuration — the width, height, x, y, and center fields inside the windows array of tauri.conf.json.
  2. Runtime APIs — Rust’s WebviewWindowBuilder and the @tauri-apps/api/window JavaScript package let you build or modify windows after the app starts.
  3. State persistence — the window-state plugin automatically saves the last size, position, and maximized state, then restores it when the app reopens. You will also need the matching plugin permissions in a capability file.

Knowing which layer overrides another helps you avoid surprises. The static config sets the baseline. Runtime calls override it for the current session. The window‑state plugin runs early in the creation cycle and will override the static config if a saved state exists — so if you set center: true but the user previously dragged the window somewhere else, the restored position wins.

Setting Initial Size and Position via Configuration

The simplest way to control a window’s geometry is to add the width, height, x, and y properties under the app.windows entry in tauri.conf.json. If you omit x and y, the window manager on each platform will decide where to place the window — typically cascading slightly from the top‑left.

{
  "app": {
    "windows": [
      {
        "label": "main",
        "title": "My App",
        "width": 1024,
        "height": 768,
        "x": 100,
        "y": 50
      }
    ]
  }
}

width and height default to 800 and 600 if you leave them out. They accept any positive integer. The values are in logical pixels, so they scale with the display’s DPI settings automatically.

When you specify x and y, the window’s top‑left corner will sit exactly at those screen coordinates. This gives you pixel‑perfect placement, but it has a few quirks worth knowing:

  • On Linux, some compositors or window managers ignore explicit initial positions (especially Wayland‑based ones). The window may still appear at a default position.
  • Negative coordinates are technically possible — they would place part of the window off‑screen, which is rarely what you want.
  • If you later add the window-state plugin, the saved position will override the static x and y unless you disable that behaviour manually.

Position and Centering Conflict:

When both x/y and center: true are present, the center flag takes precedence — the explicit coordinates are ignored and the window is centered on the primary monitor. If you need fine‑grained placement, don’t set center to true.

Centering the Window on Launch

Set "center": true and the window will appear exactly in the middle of the primary screen — horizontally and vertically — regardless of the monitor’s resolution. This is handy for utility dialogs, login panels, or any app that should always start dead‑centre.

{
  "app": {
    "windows": [
      {
        "label": "main",
        "width": 600,
        "height": 400,
        "center": true
      }
    ]
  }
}

Centering happens once at creation. If the user moves the window and closes the app, the next launch will centre it again unless you’ve set up the window‑state plugin to remember the previous position. In that case the plugin’s saved state wins.

On multi‑monitor setups, center is always relative to the primary monitor. Tauri doesn’t provide a built‑in way to centre on a non‑primary monitor from configuration alone, but you can combine runtime APIs with a monitor list to achieve that after the window exists.

Minimum and Maximum Size Constraints

You can prevent a window from being resized beyond certain dimensions. The four properties for this are minWidth, minHeight, maxWidth, and maxHeight. They accept positive integers (pixels) or null to leave the boundary unconstrained.

{
  "app": {
    "windows": [
      {
        "label": "main",
        "width": 800,
        "height": 600,
        "minWidth": 400,
        "minHeight": 300,
        "maxWidth": 1200,
        "maxHeight": 900
      }
    ]
  }
}

Once these are set, the operating system enforces the limits — the user literally cannot drag the window edge beyond them. That makes them a reliable way to guard your layout against extreme aspect ratios.

Invalid Constraint Ranges Will Prevent the Window From Building:

If maxWidth is smaller than minWidth, or maxHeight is smaller than minHeight, Tauri will fail to create the window and your app may crash on startup. Double‑check that your maximums are genuinely larger than your minimums.

If you want to lock the window to an exact size, set resizable: false or make minWidth equal to maxWidth and minHeight equal to maxHeight. However, disabling resizable is the cleaner approach — the constraints method is intended for ranges, not for emulating a non‑resizable window.

Setting Size Constraints at Runtime

Sometimes you don’t know the limits until after the app has loaded (for example, after a video renderer initializes or a user preference is read). You can apply constraints programmatically.

use tauri::SizeConstraints;
// Inside a command or setup hook, obtain the window handle
let window = app.get_webview_window("main").unwrap();
window.set_size_constraints(SizeConstraints {
    min_width: Some(400.0),
    min_height: Some(300.0),
    max_width: Some(1200.0),
    max_height: Some(900.0),
}).expect("failed to set size constraints");

These constraints take effect immediately and replace any previously set values. If the window’s current size falls outside the new bounds, the OS will resize it to fit — usually by clamping it to the nearest edge of the allowed range.

Constraints Are Enforced by the System:

Because the operating system itself prevents out‑of‑bound resizing, you don’t need to write any resize event handlers to enforce these limits. Once set, you can trust that the window will stay within the defined box.

Programmatic Window Sizing and Positioning

Beyond static configuration, you can create entirely new windows with custom dimensions from your Rust backend or your frontend JavaScript. This is useful for settings panels, preview windows, or any secondary view that needs its own geometry.

use tauri::WebviewWindowBuilder;
use tauri::WebviewUrl;
#[tauri::command]
fn open_preview_window(app: tauri::AppHandle) {
    WebviewWindowBuilder::new(&app, "preview", WebviewUrl::App("/preview".into()))
        .inner_size(640.0, 480.0)
        .position(300.0, 200.0)
        .build()
        .unwrap();
}

The inner_size method (Rust) and width/height fields (JavaScript) refer to the content area of the window — the space available to your HTML, not counting the title bar or borders. The position methods set the top‑left corner of the full window (including decorations) to the given screen coordinates.

If you only want to move or resize the current window after it already exists, you can use:

// Resize the current window
window.set_size(tauri::Size::Logical(tauri::LogicalSize {
    width: 800.0,
    height: 600.0,
})).unwrap();
// Move the current window
window.set_position(tauri::Position::Logical(tauri::LogicalPosition {
    x: 50.0,
    y: 100.0,
})).unwrap();

Permissions for Window APIs:

Creating or manipulating windows from the frontend requires that you grant the relevant permissions in your capability file (typically src-tauri/capabilities/default.json). The core:default permission includes basic window commands, but if you use more specific APIs like start_dragging, you’ll need to add those explicitly. See the Plugin Permissions section for details.

Persisting Window Size and Position Across Sessions

Users expect their desktop apps to reopen exactly where they left them — same size, same position, same maximized state. Tauri’s window-state plugin handles this automatically. Once set up, it intercepts window close events, saves the geometry to a file, and restores it when the app launches again.

Installing and Configuring the Plugin

1

Add the plugin to your project

Use the Tauri CLI to add the plugin. This updates both Cargo.toml and the frontend dependencies.

cargo tauri add window-state
2

Initialize the plugin in lib.rs

Register the plugin inside your tauri::Builder’s setup hook. The desktop‑only conditional ensures it doesn’t interfere with mobile builds.

#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
    tauri::Builder::default()
        .setup(|app| {
            #[cfg(desktop)]
            app.handle().plugin(tauri_plugin_window_state::Builder::default().build());
            Ok(())
        })
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}
3

Grant the required permissions

Add "window-state:default" to your capabilities file so the plugin can read and write the saved state.

{
  "identifier": "default",
  "description": "Capability for the main window",
  "windows": ["main"],
  "permissions": [
    "core:default",
    "window-state:default"
  ]
}

After these steps, every window you create will automatically have its state saved on close and restored on reopen. You don’t need to call any additional APIs — the plugin hooks into the application lifecycle by itself.

Verify Persistence:

Launch your app, resize the window, move it to a different spot, then close it. When you reopen the app, it should reappear at the exact same size and position. If it does, the plugin is working correctly.

Manual Save and Restore

If you need more control — for example, saving state before a window closes programmatically, or restoring only specific flags — you can call the plugin’s functions directly.

use tauri_plugin_window_state::{AppHandleExt, WindowExt, StateFlags};
// Save all open windows
app.save_window_state(StateFlags::all()).unwrap();
// Restore a specific window
window.restore_state(StateFlags::SIZE | StateFlags::POSITION).unwrap();

The StateFlags bitmask lets you choose what gets saved or restored: SIZE, POSITION, MAXIMIZED, FULLSCREEN, and VISIBLE. By default the automatic save uses ALL, but you can tailor this if, for example, you never want to restore the maximized state.

Stored Size vs. Decorations:

When you hide window decorations (decorations: false), the operating system no longer draws a title bar, so the inner content area and the outer window area become the same physical rectangle. Earlier versions of the window-state plugin sometimes saved the inner size, which could cause an incorrect restoration when decorations were absent. This has been resolved in recent releases — the plugin now saves the outer size, ensuring consistent behaviour whether decorations are on or off.

Common Pitfalls and Troubleshooting

Several configuration mistakes show up repeatedly in real projects. Being aware of them early saves debugging time.

  • Constraint ranges that don’t make sense. If maxWidthminWidth, the window cannot be created. Always check that every maximum is strictly larger than its corresponding minimum.
  • Missing permissions for frontend window creation. Calling new WebviewWindow(...) from JavaScript will fail silently unless "core:default" is listed in your capability file’s permissions array.
  • Center vs. saved state. If you set "center": true and also use the window-state plugin, the restored position will overwrite the centered placement. The plugin calls restore_state after the window is built, so the centred position is lost. To keep centering for first‑time users but honour saved positions later, omit center from your config and instead call window.center() in your setup hook only when no saved state exists.
  • Multiple monitors and coordinate assumptions. Storing absolute screen coordinates can break when the user removes a monitor or changes their layout. The window-state plugin attempts to validate that the saved position is still visible on any connected display; if not, it falls back to the system default placement.
  • Resizing to a size smaller than the minimum constraint. If you try to programmatically set a size outside the constraints you’ve defined, the OS will clamp the window to the nearest allowed dimension. No error is thrown — the window simply won’t go smaller than minWidth/minHeight or larger than maxWidth/maxHeight.