-
Notifications
You must be signed in to change notification settings - Fork 41
getting started
Five minutes, a new dock icon, and a window that opens a custom URL.
Create a plugin alongside OpenStation (anywhere under wp-content/plugins/):
<?php
/**
* Plugin Name: My Desktop Extension
*/
defined( 'ABSPATH' ) || exit;Activate it in WP Admin → Plugins.
The plugin exposes a single helper your code should use:
if ( function_exists( 'openstation_is_enabled' ) && openstation_is_enabled() ) {
// The current user has OpenStation on. Adapt behavior if needed.
}openstation_is_enabled() returns true only when the active user has the desktop_mode_mode user meta set to '1'. If the plugin is inactive, the function does not exist — always guard with function_exists().
The dock is built from the admin $menu global by default. To surface a purely virtual entry (one that isn't in the admin menu), filter openstation_dock_items:
add_filter( 'openstation_dock_items', function ( $items ) {
$items[] = array(
'id' => 'my-extension-panel',
'title' => 'My Panel',
'icon' => 'dashicons-superhero',
'url' => admin_url( 'admin.php?page=my-extension' ),
'badge' => 0,
'submenu' => array(),
);
return $items;
} );Reload the shell; the new icon appears at the end of the dock. Click it and a window opens with admin.php?page=my-extension inside it.
Every HTTP call from a OpenStation plugin should route through the framework helper instead of native fetch(). Doing so lights up the active window's title-bar modem activity dot for free, attributes the request to the activity bus (so the dev panel + plugin observers see it), and flashes red on failure with the error message exposed as the dot's tooltip.
const res = await wp.os.fetch( '/wp-json/myplugin/v1/save', {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': nonce },
body: JSON.stringify( payload ),
} );Same return type and resolution semantics as native fetch(). The third options arg attributes the request:
wp.os.fetch( url, init, {
windowId: 'my-plugin/inbox', // attribute to a specific window
silent: true, // background poll — don't pulse the dot
} );For modules compiled into a separate Vite target (a feature bundle, an external plugin), import trackedFetch from tracked-fetch:
import { trackedFetch } from '<…>/tracked-fetch';
await trackedFetch( '/wp-json/myplugin/v1/save', init, {
source: 'my-plugin/save',
} );trackedFetch finds wp.os.fetch at runtime and falls back to native fetch only during the boot window before the shell exists.
Lint enforces this. Raw
fetch()andwindow.fetch()calls fail lint. The handful of legitimate exceptions (the wrapper itself, the PWA service worker, genuinely-silent background pollers) are documented inline witheslint-disable-next-linecomments.
See javascript-reference.md for the full signature.
The shell dispatches CustomEvents on document when windows open, close, focus, or change state:
document.addEventListener( 'os-window-opened', function ( e ) {
console.log( 'Opened', e.detail.windowId, e.detail.title );
} );Enqueue this file only in OpenStation:
add_action( 'openstation_mode_init', function () {
wp_enqueue_script(
'my-extension-shell',
plugin_dir_url( __FILE__ ) . 'shell.js',
array(),
'1.0.0',
true
);
} );openstation_mode_init fires inside the parent shell render — perfect for enqueueing shell-level code.
Block OpenStation for a specific user class:
add_filter( 'openstation_mode_enabled', function ( $enabled, $user_id ) {
// Contributors stay in classic admin.
if ( user_can( $user_id, 'contributor' ) ) {
return false;
}
return $enabled;
}, 10, 2 );- Hooks Reference — the full filter + action list.
-
JavaScript Reference — the event and
postMessageAPIs. - Examples — copy-paste recipes.
This wiki is generated from the docs/ directory — edits made here are overwritten by the next sync.
To change a page, open a pull request against docs/.
Guides
- Development guide
- Releasing openstation
- Agents security model
- API Index
- Architecture
- Bridge protocol — wiring overview
- <os-*> component reference
- Native Desktop Host — Experimental
- Desktop themes
- Dock customization — two registries, one mental model
- The event-driven framework
- Files on the Desktop
- Folder sharing
- Getting Started
- Hooks Reference
- Icons
- JavaScript Reference
- The Living Tree — algorithm definition
- Mio
- Native Windows & Framework Interop
- Plugin compatibility layer
- Progressive Web App (PWA)
- Station Home
- Using openstation from your own plugin
Migration notes
- Migration: built-in activity channels move to the os/ namespace
- Migration: window, wallpaper and widget bundles load on demand
- Migration — the navigation model
- Migration: a native window's tabs move to the window chrome
All examples
- AI Agents — extend and invoke from a plugin
- wp.os.ai.ask() — programmatic AI Copilot
- Tune the AI model config
- Custom arrange-menu action
- Open a child window its owner can't cover
- Style a specific admin page inside the iframe
- Code Blue — register your plugin's log file
- Open a file in the Code editor (deep-link from any window)
- Connect to a window — title-bar button + iframe pub/sub
- Content changes — live-refresh every window listing your type
- Custom window chrome (Experimental)
- Register a custom unfocused-window effect
- Example: render a data table
- Real file storage — react to uploads, gate policy, share from PHP
- React to a window being set free onto the real desktop
- Cross-window devtools — instrumentation primitives
- Add a dock item with a badge
- Decorate the dock without forking the renderer
- Replace the dock rail entirely
- Retune the Drafts widget's AI writing assistant
- Gate OpenStation by role
- Iframe-initiated window opens
- Build a feed reader without the bookkeeping
- Inject data into openStationConfig
- Render a list without losing clicks — renderKeyedList()
- Example: layout primitives (body → panel → row → col)
- Use <os-*> components from a plugin that ships as a zip
- Restyle and drive Mio
- Add an action that works on a whole selection
- WP Explorer — custom post types and their folder
- Add an action button to a WP Explorer preview pane
- Example: native Posts window
- Example: native window with tabs
- Native windows
- Customize note → post conversion
- Send a notification
- OAuth relay — connect to an external service
- OS-file drop
- <os-flyout> — window-scoped sliding card
- Plugins window — extras
- Track who's around — wp.os.presence
- Example: progress bar
- PWA install — surface your own button
- React to window events
- Example: extend the Trash
- Register a slash-command
- Register a desktop theme from a plugin
- Register a game
- Example: register a desktop icon (Jorvy)
- Register a wallpaper
- Register a widget
- Related entities — extend the title bar's "Related" menu
- The native-window render ctx
- Programmatic folder sharing
- Share state across multi-bundle plugins — wp.os.createSharedStore()
- Example: loading spinner
- Add an opt-in card to Station Home
- Accept drops on your desktop icon
- Give a tile two icons, one per state
- Add a row to a window's ⋯ menu
- Example: window activity & the status ring
- Window controls
- Subscribe to window lifecycle events
- Window links — relate windows and restyle the ties (Experimental)
- Window loading state — spinner overlay & ready signal
- Show a banner at the top of a window
- Pulse a window's icon — Window.requestAttention()
- Register a custom window reveal
- Window slots
- Window themes
- Native window with bundle-bound config