Chrome Extension Windows API — How to Create and Manage Browser Windows
22 min readChrome Extension Windows API — How to Create and Manage Browser Windows
Introduction
The chrome.windows API is a powerful tool for Chrome extension developers who need to programmatically create, modify, and manage browser windows. Unlike basic tab management, the Windows API gives you control over the window container itself—its size, position, type, focus state, and even which display monitor it appears on. This guide covers the essential methods and use cases for building window-aware extensions.
Understanding the Windows API
The chrome.windows API provides a collection of methods that enable full window lifecycle management. Before diving into specific operations, it’s important to understand what permissions, if any, are required. For basic window creation and querying, no special permissions are needed. However, certain advanced features like accessing window bounds across all windows may require the "tabs" permission.
The API centers around several core methods: create() for opening new windows, update() for modifying existing windows, remove() for closing windows, and various get* methods for retrieving window information. Each method returns a Promise (in modern Manifest V3 extensions), making async/await patterns straightforward to implement.
Creating Windows
The chrome.windows.create() method is your primary entry point for spawning new windows. At its simplest, you can create a window with a single URL:
// Create a basic window with a specified URL
const newWindow = await chrome.windows.create({
url: "https://example.com"
});
This creates a standard browser window. However, the real power of the API emerges when you leverage its many options. You can specify exact dimensions using width and height, position the window with left and top, and control the window type with the type parameter.
Window Types Explained
Chrome supports several window types, each serving different purposes:
-
“normal” — The default browser window with full Chrome UI, including the address bar, bookmarks bar, and tabs. This is what users expect from a standard browsing experience.
-
“popup” — A minimal, temporary window without tabs or address bar. Perfect for extension popups, floating toolbars, or notification-style interfaces. These windows automatically close when Chrome loses focus in some configurations.
-
“panel” — A deprecated type that behaves similarly to popup but with additional constraints. Modern extensions should use “popup” instead.
-
“app” — Used for packaged apps, generally not applicable to typical extensions.
// Create a popup-style window for an extension tool
const popupWindow = await chrome.windows.create({
url: "panel.html",
type: "popup",
width: 450,
height: 600,
left: 100,
top: 100
});
Managing Window Focus and Incognito Mode
Focused Windows
The focused property controls whether a newly created window receives keyboard focus. By default, new windows receive focus, but you can change this behavior:
// Create window without taking focus
const backgroundWindow = await chrome.windows.create({
url: "https://example.com",
focused: false
});
This is useful when you want to open reference content without interrupting the user’s current task. You can also check and update focus on existing windows:
// Get the last focused window
const lastFocused = await chrome.windows.getLastFocused({});
// Focus a specific window by ID
await chrome.windows.update(focusedWindow.id, { focused: true });
Incognito Windows
Creating incognito (private browsing) windows requires careful consideration of privacy implications. The incognito property allows you to open private windows:
// Create an incognito window
const privateWindow = await chrome.windows.create({
url: "https://example.com",
incognito: true
});
Important restrictions apply to incognito windows: they cannot access extension pages or resources from normal windows, and service workers may have limited visibility into incognito tab activity. Always inform users when your extension interacts with private browsing data.
Updating Windows
Once a window exists, you can modify its properties using chrome.windows.update(). This method accepts a window ID and an update properties object:
// Resize and move a window
await chrome.windows.update(windowId, {
width: 800,
height: 600,
left: 200,
top: 150
});
// Change window state
await chrome.windows.update(windowId, {
state: "maximized" // "normal", "minimized", "maximized", "fullscreen"
});
// Bring window to front
await chrome.windows.update(windowId, {
focused: true
});
The update method is particularly valuable for implementing window management features like “always on top” functionality or snap-to-edge behaviors common in productivity extensions.
Removing Windows
Closing a window is straightforward but should be used thoughtfully to avoid frustrating users:
// Close a specific window by ID
await chrome.windows.remove(windowId);
Note that you cannot close the user’s last remaining browser window for security reasons. Attempting to do so will fail silently or throw an error depending on the Chrome version.
Multi-Monitor Support
Modern extensions can leverage multi-monitor setups by querying display information and positioning windows accordingly. The key is combining chrome.windows with the Display Surface API:
// Get all displays
const displays = await chrome.system.display.getInfo();
// Create window on secondary monitor
const secondaryDisplay = displays[1]; // Assuming display[0] is primary
await chrome.windows.create({
url: "https://example.com",
left: secondaryDisplay.bounds.left + 100,
top: secondaryDisplay.bounds.top + 100,
width: 1024,
height: 768
});
This capability is essential for extensions that manage workspace layouts or provide multi-monitor productivity tools. You can save and restore window positions across displays, enabling features like “move all windows to monitor 2” or workspace presets.
Querying Windows
The API provides several methods to retrieve window information:
chrome.windows.get(windowId, options)— Get a specific window by IDchrome.windows.getCurrent(options)— Get the window invoking the callbackchrome.windows.getLastFocused()— Get the window most recently focused by the userchrome.windows.getAll(options)— Get all windows
// Get all windows with their tabs
const allWindows = await chrome.windows.getAll({ populate: true });
// Find a specific window by ID
const specificWindow = await chrome.windows.get(windowId, { populate: false });
The populate: true option is particularly useful when you need to work with both windows and their contained tabs in a single operation.
Best Practices
When working with the Windows API, consider these recommendations: Always handle errors gracefully, as window operations can fail if the window is closed during the operation or if invalid parameters are provided. Use the focused property thoughtfully—unexpected focus shifts can frustrate users. For extensions that create multiple windows, implement cleanup logic to close windows when they’re no longer needed.
Test thoroughly across different Chrome configurations and multi-monitor setups, as window behavior can vary between platforms. Finally, document any assumptions about window state in your extension’s user-facing documentation.
Conclusion
The chrome.windows API provides comprehensive control over browser window creation, modification, and management in Chrome extensions. From basic window spawning to sophisticated multi-monitor layouts, mastering these capabilities enables you to build powerful productivity tools and sophisticated extension interfaces. Combined with the Tabs API, you have complete control over the user’s browsing environment.
Chrome Windows API
Introduction
chrome.windowsAPI manages browser windows for multi-window extensions- Required for picture-in-picture, auth popups, and window-based extensions
- Requires
"windows"permission in manifest.json - Reference: https://developer.chrome.com/docs/extensions/reference/api/windows
manifest.json
{ "permissions": ["windows"] }
Window Types
normal- Standard browser window with tabs/address barpopup- Minimal window for extension UIpanel- Docked panel (Chrome app behavior)app- Deprecated, use extensions instead
Window State
normal- Standard decorated windowminimized- Minimized to taskbarmaximized- Fills screenfullscreen- Fullscreen mode
Getting Windows
chrome.windows.getAll
// Get all windows
chrome.windows.getAll((windows) => {
windows.forEach(win => console.log(`Window ${win.id}: ${win.type}`));
});
// Filter by type
chrome.windows.getAll({ types: ["normal"] }, (w) => console.log(w.length));
chrome.windows.get
chrome.windows.get(windowId, (window) => console.log(window));
chrome.windows.getCurrent
chrome.windows.getCurrent((window) => console.log("Current:", window.id));
chrome.windows.getLastFocused
chrome.windows.getLastFocused((window) => console.log("Last:", window.id));
# Chrome Windows API Guide
## Overview
The Windows API provides powerful methods to manage browser windows in Chrome extensions. This guide covers window manipulation, events, and practical patterns. No special permissions needed for basic operations, but `"tabs"` permission is required for accessing tab details.
## Window Properties
- `id`: Unique window identifier
- `focused`: Boolean indicating if window has focus
- `top`, `left`, `width`, `height`: Window position and dimensions
- `state`: Current window state (normal, minimized, maximized, fullscreen)
- `type`: Window type (normal, popup, panel, app)
- `tabs`: Array of Tab objects (requires `"tabs"` permission)
- `incognito`: Boolean for private browsing windows
## Window Types
- **normal**: Standard browser window with tabs and address bar
- **popup**: Minimal window often used by extensions
- **panel**: Docked panel window (deprecated)
- **app**: Application-specific window for Chrome apps
## Window States
- **normal**: Standard windowed mode
- **minimized**: Minimized to taskbar
- **maximized**: Fills the screen
- **fullscreen**: Fullscreen mode (F11)
## Getting Windows
### chrome.windows.get — Get a Specific Window
```javascript
chrome.windows.get(windowId, { populate: true }, (window) => {
window.tabs.forEach(tab => console.log(tab.title));
});
chrome.windows.getAll — List All Windows
chrome.windows.getAll({ populate: true }, (windows) => {
console.log('Total tabs:', windows.reduce((sum, w) => sum + w.tabs.length, 0));
});
chrome.windows.getCurrent — Current Window
chrome.windows.getCurrent((window) => console.log('Current:', window.id));
chrome.windows.getLastFocused — Last Focused Window
chrome.windows.getLastFocused((window) => console.log('Last focused:', window.id));
Creating Windows
chrome.windows.create
// Basic window
chrome.windows.create((window) => console.log("Created:", window.id));
// Open URL
chrome.windows.create({ url: "https://example.com" });
// Popup
chrome.windows.create({ url: "popup.html", type: "popup", width: 400, height: 300 });
// Positioned
chrome.windows.create({ url: "p.html", left: 100, top: 100, width: 500, height: 400 });
// Maximized
chrome.windows.create({ url: "p.html", state: "maximized" });
// Incognito
chrome.windows.create({ url: "p.html", incognito: true });
Updating Windows
chrome.windows.update
chrome.windows.update(windowId, { focused: true });
chrome.windows.update(windowId, { left: 200, top: 150 });
chrome.windows.update(windowId, { width: 800, height: 600 });
chrome.windows.update(windowId, { left: 100, top: 100, width: 1024, height: 768 });
chrome.windows.update(windowId, { state: "maximized" });
chrome.windows.update(windowId, { state: "minimized" });
chrome.windows.update(windowId, { state: "normal" });
chrome.windows.update(windowId, { state: "fullscreen" });
### chrome.windows.create — Create New Windows
```javascript
chrome.windows.create({ url: 'https://example.com' });
chrome.windows.create({
url: 'popup.html',
type: 'popup',
width: 400,
height: 300,
focused: true,
state: 'maximized'
});
Modifying Windows
chrome.windows.update — Update Window Properties
chrome.windows.update(windowId, { focused: true });
chrome.windows.update(windowId, { width: 1024, height: 768 });
chrome.windows.update(windowId, { left: 0, top: 0 });
chrome.windows.update(windowId, { state: 'maximized' });
chrome.windows.update(windowId, { state: 'minimized' });
chrome.windows.update(windowId, { alwaysOnTop: true });
Closing Windows
chrome.windows.remove
chrome.windows.remove(windowId, () => console.log("Closed"));
chrome.windows.getCurrent((win) => chrome.windows.remove(win.id));
### chrome.windows.remove — Close a Window
```javascript
chrome.windows.remove(windowId);
Window Events
onCreated
chrome.windows.onCreated.addListener((window) => console.log("Created:", window.id));
onRemoved
chrome.windows.onRemoved.addListener((windowId) => console.log("Removed:", windowId));
onFocusChanged
chrome.windows.onFocusChanged.addListener((windowId) => {
if (windowId === chrome.windows.WINDOW_ID_NONE) console.log("No focus");
else console.log("Focus:", windowId);
});
onBoundsChanged
chrome.windows.onBoundsChanged.addListener((w) =>
console.log("Bounds:", w.left, w.top, w.width, w.height));
Use Cases
Picture-in-Picture Window
function openPiP(videoUrl) {
chrome.windows.create({
url: `player.html?video=${encodeURIComponent(videoUrl)}`,
type: "popup", width: 640, height: 360, alwaysOnTop: true, focused: true
});
}
Auth Popup
function openAuth() {
return new Promise((resolve) => {
chrome.windows.create({ url: "auth.html", type: "popup", width: 500, height: 600, focused: true }, (win) => {
const listener = (msg) => {
if (msg.type === "authComplete") {
chrome.windows.remove(win.id);
chrome.runtime.onMessage.removeListener(listener);
resolve(msg.token);
}
};
chrome.runtime.onMessage.addListener(listener);
});
});
}
Multi-Window Manager
const extWindows = new Set();
chrome.windows.onCreated.addListener((w) => { if (w.type === "popup") extWindows.add(w.id); });
chrome.windows.onRemoved.addListener((id) => extWindows.delete(id));
function closeAll() { extWindows.forEach(id => chrome.windows.remove(id)); extWindows.clear(); }
State Restoration
let savedState = null;
function saveState(windowId) {
chrome.windows.get(windowId, (w) => { savedState = { left: w.left, top: w.top, width: w.width, height: w.height, state: w.state }; });
}
function restoreState(windowId) { if (savedState) chrome.windows.update(windowId, savedState); }
Tab to Window
// Move tab to new window
chrome.tabs.move(tabId, { windowId: null, index: -1 }, (tab) => { chrome.windows.create({ tabId: tab.id }); });
// Get windows with tabs
chrome.windows.getAll({ populate: true }, (windows) => { windows.forEach(w => console.log(`Window ${w.id}: ${w.tabs.length} tabs`)); });
Complete Example
// background.js
const managed = new Map();
chrome.action.onClicked.addListener(() => {
const existing = managed.get("main");
if (existing) chrome.windows.update(existing, { focused: true });
else chrome.windows.create({ url: "manager.html", type: "normal", width: 800, height: 600 }, (win) => managed.set("main", win.id));
});
chrome.windows.onRemoved.addListener((id) => { if (managed.has(id)) managed.delete(id); });
Constants
chrome.windows.WINDOW_ID_CURRENT- Current windowchrome.windows.WINDOW_ID_NONE- No window focused
Best Practices
- Check window existence before updating/removing
- Use
populate: truefor tab information - Handle WINDOW_ID_NONE in focus listeners
- Store window IDs for later reference
chrome.windows.onCreated
chrome.windows.onCreated.addListener((window) => console.log('Created:', window.id));
chrome.windows.onRemoved
chrome.windows.onRemoved.addListener((windowId) => console.log('Closed:', windowId));
chrome.windows.onFocusChanged
chrome.windows.onFocusChanged.addListener((windowId) => {
if (windowId === chrome.windows.WINDOW_ID_NONE) return;
console.log('Focus:', windowId);
});
chrome.windows.onBoundsChanged
chrome.windows.onBoundsChanged.addListener((window) => {
console.log('Bounds:', window.left, window.top, window.width, window.height);
});
Building a Window Manager Extension
class WindowManager {
constructor() {
this.setupListeners();
}
setupListeners() {
chrome.windows.onCreated.addListener(w => console.log('Created:', w.id));
chrome.windows.onRemoved.addListener(id => console.log('Closed:', id));
chrome.windows.onFocusChanged.addListener(id => {
if (id !== chrome.windows.WINDOW_ID_NONE) console.log('Focus:', id);
});
}
async getAllWindows() {
return chrome.windows.getAll({ populate: true });
}
async createWindow(url, options = {}) {
return chrome.windows.create({ url, ...options });
}
async closeWindow(windowId) {
return chrome.windows.remove(windowId);
}
async focusWindow(windowId) {
return chrome.windows.update(windowId, { focused: true });
}
async minimizeWindow(windowId) {
return chrome.windows.update(windowId, { state: 'minimized' });
}
async maximizeWindow(windowId) {
return chrome.windows.update(windowId, { state: 'maximized' });
}
}
const manager = new WindowManager();
chrome.runtime.onMessage.addListener((msg, sender, response) => {
if (msg.action === 'getWindows') {
manager.getAllWindows().then(response);
return true;
}
});
Common Mistakes
- Not checking for
WINDOW_ID_NONEin focus change events - Forgetting window bounds may be undefined for minimized windows
- Not handling the async nature of window operations
- Attempting to close the last window without user interaction