-
Notifications
You must be signed in to change notification settings - Fork 41
example workspace preset
Status: Stable
A workspace is a desktop plus the answer to what it is for: which apps show on it, which windows it opens with, and how they are arranged. A template is how a plugin offers one — it appears as a card on the wizard's Start step, beside Blank desktop, and picking it mints a desk.
The whole template can live in a filter. This one is a support desk: comments and users side by side, with the helpdesk plugin's own screen leading.
add_filter(
'openstation_workspace_presets',
function ( $presets ) {
$presets[] = array(
'id' => 'support',
'label' => __( 'Support', 'my-plugin' ),
'description' => __( 'Tickets, comments and the people behind them.', 'my-plugin' ),
'icon' => 'dashicons-sos',
'color' => '#2271b1',
'layout' => 'columns',
// Match TOKENS, not ids. Each is tested as a substring
// against every navigable item's id, URL, window id and
// title — so this finds the helpdesk menu whatever slug it
// registered under.
'apps' => array( 'my-helpdesk', 'edit-comments.php', 'users.php' ),
// Widget ids, not tokens — a widget id is a registry key,
// so it is named exactly. One whose plugin is absent is
// skipped at mount: a shorter column, not a broken desk.
'widgets' => array( 'clock', 'desktop-mode/recent-comments' ),
// How the desk looks. A sparse patch over the user's own
// settings, painted on entry and handed back on exit —
// allowlisted keys only.
'appearance' => array(
'wallpaper' => 'dark',
'accent' => 'wp-blue',
),
// Windows the desk opens with. An entry whose `match` finds
// nothing is skipped, so this template is safe to ship on a
// site that has not activated the helpdesk yet.
'windows' => array(
array( 'match' => 'my-helpdesk' ),
array( 'match' => 'edit-comments.php' ),
array( 'match' => 'users.php' ),
),
// Ascending. The shipped desks claim 10 / 20 / 30, so this
// lands after them; leave it out to lead the list.
'order' => 40,
);
return $presets;
}
);That is the whole integration. The client resolves the tokens against the live navigation the same way it resolves a built-in's.
Every template keeps Dashboard, Media and Settings on top of whatever it names — a desk with no way to reach them is a dead end.
The same filter removes one. A blog with no store has no reason to be offered a Commerce desk:
add_filter(
'openstation_workspace_presets',
function ( $presets ) {
return array_values(
array_filter(
$presets,
fn( $preset ) => 'commerce' !== $preset['id']
)
);
}
);Same shape, registered on the client. Use this when the template depends on something only the browser knows:
wp.os.workspaces.registerPreset( {
id: 'support',
label: 'Support',
description: 'Tickets, comments and the people behind them.',
icon: 'dashicons-sos',
color: '#2271b1',
layout: 'columns',
apps: [ 'my-helpdesk', 'edit-comments.php', 'users.php' ],
widgets: [ 'clock', 'desktop-mode/recent-comments' ],
windows: [
{ match: 'my-helpdesk' },
{ match: 'edit-comments.php' },
],
} );os.workspaces.profile fires the moment a profile is read off a template, before the desktop is created. This adds a plugin's own screen to the Commerce desk without redefining it:
wp.hooks.addFilter(
'os.workspaces.profile',
'my-plugin/commerce-extras',
( profile, preset ) => {
if ( 'commerce' !== preset.id || 'only' !== profile.apps.mode ) {
return profile;
}
const shipping = wp.os
.getNavItems()
.find( ( item ) => item.id.includes( 'my-shipping' ) );
if ( ! shipping ) {
return profile;
}
return {
...profile,
apps: {
...profile.apps,
ids: [ ...profile.apps.ids, shipping.id ],
},
};
}
);os.workspaces.provisioned fires once per workspace, after its launch list has opened and its layout has been applied. opened is smaller than the list whenever an app it names is not installed:
wp.hooks.addAction(
'os.workspaces.provisioned',
'my-plugin/welcome',
( { desktopId, opened, layout } ) => {
const desk = wp.os.workspaces
.list()
.find( ( d ) => d.id === desktopId );
if ( desk?.profile?.preset === 'support' && opened > 0 ) {
wp.os.showToast( `Support desk ready — ${ opened } windows, ${ layout }.` );
}
}
);create() takes an explicit profile when a template is not the right shape — a desk minted for one customer, say:
wp.os.workspaces.create( {
label: `Order #${ orderId }`,
profile: {
preset: '',
icon: 'dashicons-cart',
color: '#7f54b3',
apps: { mode: 'all', ids: [] },
// Omit `widgets` (or use mode 'all') to leave the user's own
// column alone; 'only' makes the column exactly these ids
// while the desk is active, and restores theirs on the way out.
widgets: { mode: 'only', ids: [ 'desktop-mode/recent-comments' ] },
windows: [
{ match: 'wc-orders', url: `admin.php?page=wc-orders&id=${ orderId }` },
{ match: 'users.php', url: `user-edit.php?user_id=${ customerId }` },
],
layout: 'columns',
provisioned: false,
},
} );provisioned: false is what makes the launch list run when the desk is entered. Set it true and you get an arranged, empty desk.
Narrowing the rails is a view, not a settings edit: it computes the navigation with extra 'hidden' placements and leaves the user's stored navPlacement untouched. And it can never hide OpenStation's own controls — Overview, System, Trash, Exit — or an open window's tile. See Workspaces.
The widget column follows the same "writes nothing" rule by a different route: 'only' mounts exactly what it names, whether or not the user enabled those widgets globally, and hands their own column back the moment they leave. See Widgets are a layout, not a filter.
So does the look. A workspace's appearance is a sparse patch over the user's settings, restored on exit — and saving Preferences while standing on an overridden desk still writes their values back for every key they did not touch. Only allowlisted keys are honoured, on both sides. See Appearance is a view too.
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
- The App Framework — a window in one PHP file
- Architecture
- Bridge protocol — wiring overview
- <os-*> component reference
- Data model — where OpenStation keeps its data
- 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
- Window-scoped MIO
- Mio
- Mobile — the phone layer
- Multisite
- Native Windows & Framework Interop
- OpenStation Network
- Plugin compatibility layer
- Progressive Web App (PWA)
- Station Home
- Using openstation from your own plugin
- Workspaces
Migration notes
- Migration: built-in activity channels move to the os/ namespace
- Migration — Code Blue becomes an App Framework app
- Migration: window, wallpaper and widget bundles load on demand
- Migration — Posts, Pages, Users, User Edit, Plugins and Comments become App Framework apps
- Migration — the navigation model
- Migration — OpenStation Preferences becomes an App Framework app
- Performance settings move to Extended options
- Presence storage migration
- Migration — the Recycle Bin becomes an App Framework app
- Migration — the shell boots from its own screen
- Migration — Station Home becomes an App Framework app
- Migration: a native window's tabs move to the window chrome
- Migration — WP Explorer becomes the my-wordpress app
- Migration — WordPress package globals are no longer ambient
More
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
- Repairable form edits with MIO
- Register a window companion
- Pin your app to the phone tab bar, and react to the mode
- Add an action that works on a whole selection
- WP Explorer — custom post types and their folder
- WP Explorer — add a column to the list view
- 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
- Ship a window as an .os.php app
- 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
- Revisions in their own window — extend or redirect "View revisions"
- Programmatic folder sharing
- Share state across multi-bundle plugins — wp.os.createSharedStore()
- Example: loading spinner
- Add an opt-in card to Station Home
- Observe stored-file cleanup failures
- 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
- Place something where the user can reach it — wp.os.workArea
- Ship a workspace template