Desktop Mode is a WordPress plugin that turns /wp-admin into a desktop-style interface with movable windows and a dock menu. It’s opt-in per user, doesn’t change core, and fully reverts on deactivation.
A WordPress plugin that reimagines /wp-admin as a desktop operating system. Admin screens open as draggable, resizable, minimizable windows on a desktop, with a left-edge dock built from the admin menu. Purely opt-in per user — the classic admin stays untouched for everyone else, and deactivating the plugin restores vanilla Core exactly.
Zero Core patches. Every feature is wired through public WordPress hooks.
Per-user opt-in
Admin-bar toggle sets the desktop_mode_mode user meta. A dedicated /desktop-mode/ portal URL auto-enables desktop mode for first-time visitors (gated by desktop_mode_portal_auto_enable) and the admin_init redirect sends opted-in users from /wp-admin/ to the portal (desktop_mode_admin_redirect_to_portal).
Desktop shell
Fixed-viewport desktop that overlays /wp-admin: wallpaper area, unified dock (placement picked in OS Settings — left / right / bottom, default bottom), right-column widget layer, and full windowing system. desktop_mode_mode_init, desktop_mode_shell_before / _after, and the desktop_mode_shell_config filter are the main extension points.
Window system — iframe + native
Iframe windows load admin pages with ?wp_desktop=1 (chromeless mode). Native windows render directly in the parent DOM via desktop_mode_register_window() / wp.desktop.registerWindow() — multi-tab native windows are supported through desktop_mode_register_window_tab(). Both types share drag, resize, minimize, maximize, close, fullscreen, and detach-to-new-tab.
Dock
One unified rail hosting every admin menu — core and plugin alike — plus shell-level system tiles. Placement (left / right / bottom) is the user’s OS Settings preference. Core menus are ordered before plugin menus; per-item hiding via desktop_mode_dock_placement ('hidden'). Per-item multi-window support via desktop_mode_dock_item_multi. Letter-badge icon fallback for plugins without icon art.
Virtual desktops (“Spaces”)
Multiple desktops per user, each with its own window set. Overview grid (zoom-out view) surfaces the Spaces switcher, thumbnails, and create/close controls.
Arrange & snap
Admin-bar Arrange menu: Cascade, Tile, Overview, Snap to grid. Plugins contribute custom entries via desktop_mode_arrange_menu_items and react to clicks via desktop-mode.arrange.custom-action. Tile grid dimensions and snap cell size are both filterable.
Wallpaper registry
Server- and client-side registration (desktop_mode_register_wallpaper() / wp.desktop.registerWallpaper()). CSS presets + canvas (WebGL/2D) wallpapers with collision-aware surface data (wp.desktop.getWallpaperSurfaces()) for snow/rain/physics effects. In-panel renderEditor callback for custom controls, shared vendor-module loader (pixijs pre-registered).
Widgets
Right-column floating cards, optionally draggable / resizable outside the column. desktop_mode_register_widget() / wp.desktop.registerWidget(). Built-in clock. User placement persists per-user in localStorage.
Desktop icons
Wallpaper-layer shortcuts via desktop_mode_register_icon() — targets a registered native window or an admin URL.
AI Assistant + slash commands
Cmd+K palette backed by an OpenAI agentic loop (search_posts, search_pages, search_comments tools). Admin-configured API key + model picker. Auto-analysis on save_post / term / comment save with per-entity prompt filters. wp.desktop.registerCommand() adds slash commands with autocomplete (suggest()), confirm dialogs (ctx.confirm()), and full lifecycle hooks (before-run / after-run / error). Built-in /open [window] is extensible via desktop-mode.open-command.items.
Palette registry
Cmd+K cycles through all registered palettes (wp.desktop.registerPalette()) — the AI assistant is palette 0 by default; additional plugin overlays share the shortcut.
Cross-frame drag bridge
Media-library attachments drag across iframe boundaries via coordinated postMessage. Site-wide toggle through the Extended Options REST endpoint.
Toast notifications
Shell-level toasts rendered via the <wpd-toast> component. Plugins register their own tone/icon via the desktop_mode_toast_types filter. Iframe pages raise a toast through the desktop-mode-notification bridge message — it survives the iframe’s own lifecycle.
OS Settings
Native-window settings panel: wallpaper picker (with HD-only media filter), accent color swatches + custom gradient editor, dock size slider, AI platform config, and per-user default-on-startup window. Persisted via /desktop-mode/v1/os-settings.
Session persistence
Full window stack (including desktops, focus, state) is debounce-saved to /desktop-mode/v1/session and restored without layout flicker. Viewport-shrink clamping keeps off-screen windows reachable.
postMessage bridge
Typed messages for title changes, navigation (same-origin validated), focus, color-scheme sync, screen-meta panels (Screen Options / Help), external-link capture, iframe-ready handshake, and observability (iframe-error, iframe-network).
UI component library
~25 <wpd-*> web components (wpd-button, wpd-menu, wpd-panel, wpd-range-field, wpd-swatch, wpd-toast, wpd-tabs, …) available to plugin authors — rendered server-side via desktop_mode_component() or imported in TS.
i18n
Full gettext coverage across PHP and TypeScript; Spanish translation shipped. Strings go through wp.i18n (__, _x, _n, sprintf) directly — no shell-specific re-export.
Component registration API
Stable desktop_mode_register_* functions for windows, widgets, wallpapers, icons, and window tabs. All return true / WP_Error with documented error codes.
Public hook API
Comprehensive PHP and JS hook surface — dock items, placement, multi-window, native-window lifecycle, widget lifecycle, wallpaper lifecycle + surfaces, window lifecycle, iframe observability, arrange actions, virtual-desktop transitions, palette registration, command lifecycle, batch close, AI prompt + model + post-type filters, accents, toast types, default wallpaper. See docs/hooks-reference.md and docs/javascript-reference.md.
wp.desktop.mode = 'desktop' | 'tablet' | 'mobile' surface.See docs/architecture.md for how the pieces fit together and docs/hooks-reference.md for the hook surface (current and planned).
See docs/architecture.md for how the pieces fit together and docs/hooks-reference.md for the hook surface (current and planned).
.
├── desktop-mode.php # bootstrap: header, constants, require_once of includes/
├── includes/ # PHP subsystems
│ ├── helpers.php admin-bar.php ajax.php
│ ├── assets.php render.php portal.php
│ ├── session.php default-window.php components.php
│ ├── os-settings.php extended-options.php
│ ├── accents.php wallpapers.php toast-types.php
│ ├── media-query.php
│ └── ai-copilot/ # AI assistant (OpenAI client, analysis, search, jobs)
├── assets/ # compiled CSS + JS (Vite output; tracked in git)
│ ├── css/ desktop.css, windows.css, dock.css, chromeless.css, variables.css
│ └── js/ desktop.js, desktop.min.js, chromeless bridge, media-library enhancements
├── src/ # TypeScript source — compiled by Vite
│ ├── desktop.ts / dock.ts / hooks.ts / commands.ts / palette-registry.ts
│ ├── ai-assistant.ts / drag-bridge.ts / toast.ts / desktop-icons.ts
│ ├── native-windows.ts / built-in-commands.ts / public-api.ts / types.ts
│ ├── window/ # Window class — DOM, pointer, tabs, iframe bridge
│ ├── window-manager/ # stack, desktops, arrange, snap, overview
│ ├── wallpapers/ # registry, layer, surfaces, server sync, vendor loader
│ ├── widgets/ # registry, layer, frame, picker, storage
│ ├── settings/ # OS Settings panel sections
│ ├── ui/ # <wpd-*> web components
│ ├── modules/ # vendor-script lazy-loader
│ └── plugins/ # built-in demos (animated-logo-wallpaper)
├── docs/ # developer-facing docs (source of truth for plugin authors)
├── tests/ # PHPUnit + Vitest
├── languages/ # .po / .mo (es shipped)
├── bin/ # package-zip helpers
├── package.json # devDeps (vite, typescript, vitest)
├── vite.config.js # Vite lib-mode: src/desktop.ts → assets/js/desktop[.min].js (IIFE)
├── vitest.config.ts
└── tsconfig.json
Just want to try it? Grab the pre-built zip and upload it to any WordPress — Studio by WordPress.com, wp-env, or a hosted site. No Node, no build step.
desktop-mode.zip from the latest release (or pick a specific version from the releases page).For hacking on the plugin: clone the repo, run the build in watch mode, and load it into a local WordPress via symlink so every save is one browser refresh away.
npm install
The plugin uses Vite in library mode. esbuild handles transpile and minify, so builds finish in ~70 ms per bundle.
Full build — produces every bundle (npm run build:desktop, :iframe-bridge, :recycle-bin, :posts-window):
npm run build
Writes:
assets/js/desktop.js / .min.js — main shell bundle (loaded based on SCRIPT_DEBUG).assets/js/iframe-bridge.js / .min.js — opt-in bridge that gives any same-origin iframe access to wp.desktop.iframe.*.assets/js/recycle-bin.js / .min.js — Recycle Bin native window.assets/js/posts-window.js / .min.js — Native Posts window (the <wpd-table> replacement for the edit.php iframe; opt-in per user via OS Settings → Features).Development watch — auto-recompiles the unminified bundle on save:
npm run dev
Leave it running in a separate terminal; refresh the browser after each save. Set define( 'SCRIPT_DEBUG', true ) in wp-config.php so WordPress picks up the unminified bundle during development.
You need a running WordPress to load the plugin into. Pick whichever is easier.
Run npm run package to build a zip from HEAD (with correct 0644 / 0755 permissions), then follow the Quick install steps 2–3 to upload and activate it. Re-package and re-upload after each change.
If you changed source, run
npm run buildbeforenpm run package— the Vite output is gitignored, andbin/package.shsplices the built files into the zip from your working tree.
wordpress-develop and symlinkGives you the full dev loop: npm run dev rebuilds on save, a browser refresh picks it up.
# clone Core's Docker-based dev host alongside this repo
git clone https://github.com/WordPress/wordpress-develop.git
cd wordpress-develop
npm install
# symlink this plugin into the WP plugins directory
ln -s "$(pwd)/../alcazaba-plugin" src/wp-content/plugins/desktop-mode
# boot + install WordPress
npm run env:start # nginx + PHP + MySQL in Docker
npm run env:install # installs WordPress
Site: http://localhost:8889
Admin: http://localhost:8889/wp-admin/
Credentials: admin / password
Stop the environment with npm run env:stop (from the wordpress-develop directory). Activate the plugin per Quick install steps 2–3.
This plugin is built to be extended. Every significant behavior is hookable — drop an icon on the desktop, add a dock item, gate desktop mode by role, react to window events, or register a native window, all from your own plugin with zero patches here.
See docs/ — the developer documentation index.
Quick links:
window.wp.desktop API, and the iframe postMessage bridge.GPLv2 or later. See LICENSE.