-
Notifications
You must be signed in to change notification settings - Fork 41
use from a plugin
This doc explains how a sibling WordPress plugin can use openstation's TypeScript types and component classes (OsLog, OsCode, OsTabs, …) without publishing openstation to npm and without reaching into its src/ tree via relative paths.
Shipping a plugin as a zip? Start with
wp.os.loadComponents(). Thefile:dependency below is for a plugin developed beside this repo in one checkout, and it resolves at install time — a contributor cloning your plugin on its own has no../openstationto point at. If what you want is a working<os-switch>on a site that has OpenStation installed somewhere, oneawaitgets you the whole kit at runtime with no build-time relationship at all:await wp.os.loadComponents( [ 'os-switch', 'os-number-field' ] );The rest of this doc is about the build-time route: types, class imports, and the smaller bundles you get from importing only what you render.
openstation's package.json is "private": true (so it can never be accidentally npm published) but exposes its public API via the exports map. Any sibling plugin can install it as a local file dependency:
cd my-plugin
npm installThat's it. After install, the import resolves through openstation's exports:
import { OsLog, type OsLogRowRenderer, HOOKS } from 'openstation';No relative paths, no monorepo refactor, no npm registry.
Everything re-exported from src/public-api.ts:
-
TypeScript types —
WindowConfig,WallpaperDef,WidgetDef,DragManagerApi,DragBridgePayload,WindowConnection, … -
Component classes —
OsLog,OsCode,OsTabs,OsAvatar,OsBadge, … -
Hook constants —
HOOKS.WINDOW_OPENED,HOOKS.CONNECTION_OPENED,HOOKS.WINDOW_FOCUSED, … -
Public surface helpers —
DRAG_EVENTS,DRAG_BRIDGE_EVENTS, etc.
Both runtime values AND types — the same file backs both conditions in the exports map.
os-* custom elements are side-effect registered at import time, per bundle — they are not all registered globally by desktop.min.js. The shell bundle registers only a core subset and pre-loads shell-overlays.min.js (the toast / confirm-dialog / context-menu / menu / select / window-chrome kit) right after first paint; every other tag upgrades only once a loaded bundle has imported its module. Emitting a tag that no loaded bundle has imported renders inert HTML, and the missing-component warner logs a console.error with the exact import line to add.
For plugin bundles the fix is built in: any import from 'openstation' registers every tag as a side effect (the package entry re-exports the component barrel). Once your bundle imports it, templates can just emit the markup:
<os-log id="agent-trace" max-rows="500"></os-log>const log = document.getElementById( 'agent-trace' );
log.push( { level: 'info', message: 'Agent started' } );See components-reference.md for the full tag → class → source mapping. Beyond registration, the named class import (import { OsLog } from 'openstation') is useful for:
-
TypeScript type-checking of the element handle (
document.getElementById('agent-trace') as OsLog). - Subclassing a component to override behavior.
-
Programmatic instantiation (
new OsLog()thendocument.body.appendChild(el)) — rare; the HTML route is preferred.
If your plugin bundles its own JS (Vite, esbuild, webpack, …) and imports OsLog, the bundler will include the component's source in your bundle by default. For a single-component import this is ~3 KB gzip; for the full kit it's much more.
The runtime route avoids this entirely. await wp.os.loadComponents( [ … ] ) registers the tags from the shell's own copy — nothing about the components enters your bundle, and you need no file: dependency to reach them. The trade is that the kit bundle is all-or-nothing: 309 KB raw / 77 KB gzip, fetched once, cached, and only by plugins that ask. Two or three components? Import them. Want the kit, or want to ship a zip with no build-time link to this repo? Load it. See examples/load-components.md.
If instead you want to externalize — keep the import syntax but resolve it to a runtime global rather than your own bundle — your bundler config needs:
// vite.config.js
export default {
build: {
rollupOptions: {
external: [ 'openstation' ],
},
},
};Combined with a small browser shim that resolves the import to a runtime global. Note that no such global exists today — the shell publishes window.wp.os (an API surface: methods, stores, registries), not a module namespace of component classes, so an external: [ 'openstation' ] build has nothing to resolve against at runtime. If you were reaching for this to avoid duplicate bytes, wp.os.loadComponents() above is the supported answer; if you need the classes (subclassing, instanceof, programmatic construction), the file: dependency is still the route, and opening an issue about a class-namespace global is worthwhile.
We could. We deliberately don't, because:
- Bundle distribution within WordPress.org plugin reviews is already covered by the build step in this repo (
assets/js/*.min.js). Publishing a parallel npm artifact would be a second source of truth that drifts. - The TypeScript surface is the contract; type-only consumers don't need an npm fetch — they need a path.
-
file:dependencies are stable, version-locked at install time, and don't require a registry.
If you genuinely need a registry-based install (e.g. a CI runner that can't see this repo's filesystem), open an issue and we'll re-evaluate.
If your plugin code runs inside a chromeless wp-admin iframe and calls
wp.os.iframe.whenWindowId(), be aware that the returned Promise
never rejects. When the page is not running inside OpenStation (a
cross-origin parent, a direct admin URL visit, a unit-test harness) the
Promise simply hangs forever — any await after it will never resume.
Always guard with isParentReachable() first:
if ( ! wp.os.iframe.isParentReachable() ) {
return; // Not inside OpenStation — skip iframe-bridge code.
}
const windowId = await wp.os.iframe.whenWindowId();The same caveat applies to Window.whenContentReady() on the shell side —
it never rejects if the content iframe never signals readiness.
-
Cannot find module 'openstation'— confirm thefile:path is correct relative to your plugin'spackage.jsonlocation, then re-runnpm install. - Types resolve but runtime imports fail — your bundler is configured to externalize without a runtime shim. Either remove the externalization or wire a global resolver.
-
Defining
os-*elements twice — the elements register themselves on import through a guardeddefineComponent()that silently skips already-defined tags, so loading both your bundle anddesktop.min.jsis a no-op (the first-loaded class wins for each tag); no browser warning is logged. -
Editing
desktop-mode/src/*doesn't reflect in your plugin —file:dependencies on some npm versions copy at install time. Runnpm installagain, or switch tonpm linkfor an active symlink while developing both packages in parallel.
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