Library / SDK
leanflutter/window_manager avatar
leanflutter/window_manager

window_manager is now a Dart face over nativeapi, and 0.6.x removed the old behaviours

The window_manager plugin provides comprehensive window management capabilities for Flutter desktop applications, enabling full control over window size, position, appearance, close behavior, and listening to events.

850 stars317 forksDartMIT

At a glance

What is it?
window_manager gives Flutter desktop apps control over their own windows, and version 0.6 rebuilds it on the nativeapi C++ core shared by macOS, Windows and Linux. The old API survives only as a deprecated bridge, docking is gone, and the newest GitHub release tag is v0.5.0 from 2025-05-29.
Who is it for?
window_manager is the right choice for a Flutter desktop app that needs a custom title bar, a minimum size, a centreable window or a confirm before closing, since those are exactly the operations the plugin exposes and the platform runners no longer need any setup for.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 12 days ago.
What is it written in?
Mainly Dart, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

A Flutter app controlling its own desktop window

window_manager lets Flutter desktop apps size, move, show, hide and decorate their own window. That sounds small until you try to build a desktop app without it, because the default Flutter desktop runner gives you a window you cannot resize to your own constraints, cannot centre, cannot give a custom title bar, and cannot ask the user a question before closing.

The plugin covers the whole desktop surface, macOS, Windows and Linux, as the platform table in the README shows, and the topic list on the repository includes flutter-web as well, though the README's own table only claims the three desktop platforms.

The API shape in 0.6.x is straightforward. You get the current window, set a title, set a size, set a minimum size, centre it and show it.

dart
final window = WindowManager.instance.getCurrent()!;

window.title = 'window_manager';
window.setSize(const Size(1000, 700).toNative(), false);
window.minimumSize = const Size(640, 480).toNative();
window.center();
window.show();

Note the getCurrent() call, which returns a nullable, and the bang. A window may not exist yet at the moment you ask, which is a detail that bites in startup code paths.

The documentation lives at leanflutter.dev/documentation/window_manager, and there is a Chinese README in the repository as README-ZH.md. The package is published on pub.dev, and the community runs on Discord.

The plugin is a binding; nativeapi is the implementation

The most important line in the README is at the very top, above the title, in a blockquote: window_manager is built on nativeapi, a Flutter binding of one C++ core library shared by macOS, Windows and Linux.

That changes how you should read the package. It is not a self-contained plugin with per-platform implementations. The C++ core does the work, nativeapi binds it to Flutter, and window_manager is the layer on top with a Dart API shaped for window control. One core library means one set of behaviours across three platforms, and it means the platform differences are resolved in C++ rather than in three separate Dart plugin implementations.

It also explains the removal of platform plugin code. The upgrading section says builds no longer need the plugin's own setup in MainFlutterWindow.swift, my_application.cc or the Windows runner, because there is no platform plugin code left. For a team upgrading an existing app, that removes three files of glue, which is a genuine simplification, but it also means the old per-platform configuration in those files is now dead weight.

The repository layout matches this design. The lib/ directory holds the Dart API, with the entry points window_manager.dart, windowing.dart and legacy.dart all living in the same package, and there is a test/ directory alongside an example/ app that carries its own linux, macos and windows folders.

Native units on both sides of the bridge

There is a friction point in the API that is easy to miss and impossible to ignore later.

Window takes and returns nativeapi's Size, Point, Rectangle and Color. Those types are not exported from the package, because Flutter has its own versions of the same names, and shipping both would collide. The consequence is a conversion at every boundary: toNative() turns Flutter's Size into a native one, and toSize(), toOffset(), toRect() and toColor() turn them back.

That is why the usage example reads setSize(const Size(1000, 700).toNative(), false) and minimumSize = const Size(640, 480).toNative(). A Flutter Size is in logical pixels, and the native call needs device independent pixels in the platform's own convention, so the conversion is not decorative.

The same applies to event payloads. The resize event example converts back with event.newSize.toSize(), because the event carries a native size.

If you are porting 0.5.x code, this is where most of the churn will be. Every call that touched a size, a position, a rectangle or a colour needs a toNative() wrapper, and every read of a value from an event needs the inverse conversion.

Events per window, and a close request you can cancel

Event handling in 0.6.x is per window, and the README makes that explicit: every window event of every window of this app.

dart
WindowManager.instance.addListener((event) {
  switch (event) {
    case WindowFocusedEvent():
      debugPrint('window ${event.windowId} focused');
    case WindowResizedEvent():
      debugPrint('window ${event.windowId} is now ${event.newSize.toSize()}');
    default:
      break;
  }
});

Each event carries a windowId, which is what makes a multi-window app workable. The listener is attached to the manager, and events from every window arrive on it.

The second pattern is the one that solves a real problem. A window can have its own listener, and a close request can be cancelled.

dart
window.addListener((event) async {
  if (event is WindowCloseRequestedEvent && !await confirmClose()) {
    event.request.cancel();
  }
});

The README notes that close requests reach only the window's own listeners, so a per-window handler is the right place for a per-window confirmation dialog. This is how you ask the user whether to discard unsaved work, which the default runner cannot do for you.

One behavioural change matters here. The upgrading notes say that onWindowResized and onWindowMoved arrive together with onWindowResize and onWindowMove, because nativeapi reports one event per change, not a stream event followed by a final one. The old code that assumed a terminating event has to be rewritten as an accumulator that simply tracks the latest value.

A title bar you draw yourself, with drag and resize areas

The decorative half of the plugin is the reason some apps adopt it. A window with no title bar of its own, dragged and resized by widgets, is a supported configuration.

dart
final window = WindowManager.instance.getCurrent()!;
window.titleBarStyle = TitleBarStyle.hidden;

// In the app:
DragToResizeArea(
  child: Column(
    children: [
      WindowCaption(
        brightness: Theme.of(context).brightness,
        title: const Text('window_manager'),
      ),
      Expanded(child: body),
    ],
  ),
)

The title bar style is set to hidden, and then the app supplies the chrome. DragToResizeArea handles the pointer work of dragging and resizing the window from the widgets you place inside it, and WindowCaption is a ready made caption widget that takes a brightness and a title.

This is the pattern a cross-platform desktop app needs when it wants a consistent look on all three platforms, and it is where the plugin earns its place. The alternative is to accept whatever the operating system draws, which on Linux in particular varies by window manager and theme.

Multi-window work has a parallel entry point. With Flutter's multi-window API, the windowing.dart library answers the Window behind each of Flutter's window controllers.

dart
final window = controller.nativeWindow; // or nativeWindowOf(controller)
window?.title = 'Inspector';

So a Flutter controller gives you a native window handle, and the same window API applies to it.

The 0.5.x API survives in a bridge that is marked for deletion

If you are upgrading, the migration path is documented in detail, and it is not a small one.

Code written for 0.5.x keeps working by importing package:window_manager/legacy.dart instead of package:window_manager/window_manager.dart. That bridge provides the old windowManager, WindowListener, WindowOptions and calcWindowPosition on top of the native API.

The README is blunt about the status. The import has to change on purpose, legacy.dart is a bridge and not the future of the package, and its classes are marked @Deprecated and will be removed in a later release.

dart
import 'package:window_manager/legacy.dart';

await windowManager.ensureInitialized();
await windowManager.waitUntilReadyToShow(
  const WindowOptions(size: Size(1000, 700), center: true),
  () async {
    await windowManager.show();
    await windowManager.focus();
  },
);

The removals are the part to read twice. Aero-snap docking is gone: isDockable() answers false, isDocked() answers null, dock() does nothing, undock() answers false, and the docked and undocked events never fire. Keyboard grabbing on Linux is gone the same way. The system menu popup now opens at the cursor on Windows and on Linux window managers that have one, and does nothing on macOS. Arguments the core library has no use for are accepted and ignored, which covers vertically on maximize, animate on setPosition and setBounds, and visibleOnFullScreen on setVisibleOnAllWorkspaces. And getId() answers nativeapi's own window id, not the NSWindow number or the HWND, so any code that was matching on platform identifiers needs changing.

Installing the package and what it needs from the build machine

The package is added to pubspec.yaml in the usual way, and the README documents both a version constraint and a git dependency.

yaml
dependencies:
  window_manager: ^0.6.0

The git form tracks the main branch directly.

yaml
dependencies:
  window_manager:
    git:
      url: https://github.com/leanflutter/window_manager.git
      ref: main

The build requirements have moved since 0.5.x, which required Flutter 3.3 and macOS 10.11. The current requirements are Flutter 3.47 or Dart 3.13 or later, and macOS 10.15 or later. Linux build machines need GTK 3, X11 and Xi development files, installed with the package manager.

bash
sudo apt-get install libgtk-3-dev libx11-dev libxi-dev

On the release side, the tags tell a slightly confusing story. The newest GitHub release is v0.5.0 from 2025-05-29, before v0.4.3 and v0.4.2 from November 2024, yet the README's own installation snippet asks for ^0.6.0. The 0.6 line therefore exists on pub.dev without a matching GitHub release tag, and the last push to the repository was on 2026-10-11. If you pin versions, pin the pub.dev version, and read CHANGELOG.md in the tree rather than the release list.

The project is MIT licensed, with a LICENSE file at the root and a README section for contributors, alongside an all-contributors configuration file.

Editorial conclusion

window_manager is the right choice for a Flutter desktop app that needs a custom title bar, a minimum size, a centreable window or a confirm before closing, since those are exactly the operations the plugin exposes and the platform runners no longer need any setup for. It is the wrong choice if you depend on Aero-snap docking, keyboard grabbing, or the 0.5.x event model, because those were removed rather than deprecated, and the 0.5.x API now lives in a legacy.dart bridge that the README says will be deleted. Before upgrading, check that you are on Flutter 3.47 or later with macOS 10.15, install the GTK, X11 and Xi development files on Linux, and read the upgrading section, since the removal list is long and the silent parts are the resize and move events.

Frequently asked questions

How do I add window_manager to a Flutter project?

Add window_manager: ^0.6.0 to the dependencies in pubspec.yaml, or point the dependency at the git repository with ref main. Builds need Flutter 3.47 or Dart 3.13 or later and macOS 10.15 or later, and Linux build machines need the GTK 3, X11 and Xi development files.

How do I ask the user before a window closes?

Attach a listener to the window and cancel the close request. The README shows a handler that checks for WindowCloseRequestedEvent and calls event.request.cancel() when a confirmClose() call returns false, and notes that close requests reach only the window's own listeners.

What changed between window_manager 0.5.x and 0.6.x?

0.6.x is built on the nativeapi C++ core, so there is no platform plugin code left and no setup needed in MainFlutterWindow.swift, my_application.cc or the Windows runner. Aero-snap docking and Linux keyboard grabbing were removed, resize and move events now arrive as one event per change, and the old API is available only through the deprecated legacy.dart bridge.

How do I make a window without a title bar that I can drag by widgets?

Set window.titleBarStyle to TitleBarStyle.hidden, then wrap your own chrome in DragToResizeArea and use the WindowCaption widget for the caption. The README shows this as a Column with a WindowCaption and an Expanded body inside the drag area.

Does window_manager work with Flutter multi-window?

Yes. The windowing.dart library exposes the Window behind each of Flutter's window controllers, through controller.nativeWindow or nativeWindowOf(controller), and that window takes the same API as the one returned by WindowManager.instance.getCurrent().

Official sources

  1. leanflutter/window_manager on GitHub
  2. License: MIT
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/leanflutter-window-manager.svg)](https://hysenlabs.com/projects/leanflutter-window-manager)

Was this useful?