-
Notifications
You must be signed in to change notification settings - Fork 41
example recycle bin
The Trash window (id desktop-mode-recycle-bin) catches deleted attachments into the WordPress trash and lists every trashed post / page / media item with restore + permanent-delete actions. It's filterable end-to-end so plugins can add post types, columns, audit logging, or custom capability gates.
Status: Experimental. Hook names are stable; the JS column-filter shape may grow.
add_filter( 'openstation_recycle_bin_capture_post_types', function ( $types ) {
$types[] = 'product';
return $types;
} );That's it — products that go through wp_trash_post() will now show up alongside posts/pages/media. The bin uses the same delete_post capability gate WP applies to untrash, so per-CPT cap maps just work.
add_action( 'openstation_recycle_bin_after_restore', function ( $post_id ) {
$user = wp_get_current_user();
error_log( sprintf(
'[recycle-bin] %s restored #%d',
$user->user_login,
$post_id
) );
} );The matching ..._after_purge action gives you the post type as the second arg, so you can keep separate counters for media vs. content.
By default the bin shows up for anyone with edit_posts. Lock it down to administrators:
add_filter( 'openstation_recycle_bin_user_can_use', function () {
return current_user_can( 'manage_options' );
} );The window + icon registrations are skipped entirely when the gate returns false — there's no hidden UI footprint.
When a plugin needs to permanently delete an attachment without the round-trip through trash, pass the force flag:
wp_delete_attachment( $attachment_id, true );wp_delete_attachment( $id, true ) skips capture automatically — the bin only records items that travel through the WP trash. A per-deletion escape-hatch filter (openstation_recycle_bin_should_capture) is a reserved name in the hooks reference but is not yet fired — don't subscribe to it until its status flips.
By default media files are NOT auto-routed through Trash: vanilla WordPress permanent-deletes attachments on first click, and the Trash tracks only posts, pages, and comments. Sites that want media in the bin should define MEDIA_TRASH in wp-config.php:
define( 'MEDIA_TRASH', true );Once set, deleting from the Media library routes the attachment through the WP trash and the Trash window surfaces it automatically (attachment is already in the default openstation_recycle_bin_capture_post_types list). The constant has to live in wp-config.php because Core locks it before any plugin loads.
The Trash toolbar reflects this gate visually: the Media filter segment is hidden unless MEDIA_TRASH is on, so users on the default WP setup don't see a tab that can never have rows under it.
The JS layer applies a filter to the column descriptor before assignment:
wp.hooks.addFilter(
'openstation.recycleBin.columns',
'myplugin/owner-column',
( cols ) => [
...cols,
{
key: 'deleted_by_id',
label: 'Owner',
sortable: true,
render: ( _v, row ) => row.deleted_by_id
? `User #${ row.deleted_by_id }`
: '—',
},
],
);To populate a brand-new field on the row, mirror it on the PHP side via openstation_recycle_bin_item:
add_filter( 'openstation_recycle_bin_item', function ( $item, $post ) {
$item[ 'department' ] = (string) get_post_meta( $post->ID, '_dept', true );
return $item;
}, 10, 2 );The type_label field on every row carries a human-readable label for the entity kind (Post, Page, Media, Comment, or the CPT's singular label). The bin renders it as a small inline badge next to the title; column-filter authors can also reuse it for their own cells. Override it from the same filter if your CPT needs a custom label:
add_filter( 'openstation_recycle_bin_item', function ( $item, $post ) {
if ( 'product' === $post->post_type ) {
$item[ 'type_label' ] = __( 'Catalog item', 'myplugin' );
}
return $item;
}, 10, 2 );If you run a websocket or SSE service alongside WordPress, hook the unified signal so you don't have to subscribe to every delete action individually:
add_action( 'openstation_recycle_bin_signal', function ( $ts_ms ) {
My_Realtime::publish( 'recycle-bin-changed', [ 'ts' => $ts_ms ] );
} );The action fires once per request that triggered a delete (coalesced across multiple bulk-trash hooks in the same request).
The Media Library (or any other window) can re-fetch when items leave/return the trash:
document.addEventListener( 'os-recycle-bin-changed', ( e ) => {
const { kind, ok, errors, source } = e.detail;
// `source` is 'local' | 'chromeless' | 'heartbeat'.
if ( kind === 'restore' && ok > 0 ) {
myPlugin.refreshMedia();
}
} );Or via the hook bus:
wp.hooks.addAction(
'openstation.recycleBin.changed',
'myplugin/refresh-media',
( payload ) => myPlugin.refreshMedia( payload ),
);- Hooks reference — Trash — every filter and action with full signatures.
-
Data table example — the
<os-table>primitive the bin renders. -
Native windows — the
openstation_register_window()API the bin builds on.
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