-
Notifications
You must be signed in to change notification settings - Fork 41
data model
Everything the plugin persists, and how the pieces relate: eight plugin-owned tables, two custom post types, and a set of keys in WordPress's own meta and options tables — plus the caches, the upload directories and the cron hooks around them. Read this before adding a store, renaming a key, or asking "where does X live?".
The inventory is taken from the code under includes/ and apps/, not
from memory. When the code and this page disagree, the code wins and this
page has a bug.
| Data | Store | Why there |
|---|---|---|
| Desktop tiles, folders, uploaded files, shares, game scores and challenges | Plugin-owned tables ({$wpdb->prefix}desktop_mode_*) |
Relational, high-cardinality, queried by owner / parent / state. Serialised blobs in options or meta would not index. |
| Agent conversations, sticky notes | Custom post types in wp_posts
|
They are content: they get trash, capabilities, revisions and REST for free. |
| AI agents | Rows in wp_users
|
Authorship and capabilities come from Core; the profile lives in user meta. |
| Per-user preferences, session, opt-in, play time | wp_usermeta |
Follows the user; get_user_meta() is cached per request. |
| Site-wide flags, schema versions, uploaded themes | wp_options |
One value per site; the hot ones are autoload = no. |
| Computed caches (content graph, Woo aggregates, feeds) | Transients | Regenerable; never a source of truth. |
| Uploaded file bytes, theme ZIP contents | Disk under uploads/
|
The database holds names and hierarchy; the disk is a dumb blob store. |
Every stored name that still reads desktop_mode_* / desktop-mode-* is
the pre-rebrand spelling and is frozen: those are tables, options and
meta keys holding rows on live installs. Renaming a constant does not
migrate anything — it points the code at an empty place. A real rename goes
through includes/migrations.php. See AGENTS.md for the
full list of frozen values.
Neither diagram shows a MySQL FOREIGN KEY, because there are none —
WordPress Core declares none either. Integrity is kept in code: the Files
cascade-cleanup hooks, the tombstones table, and the daily prune cron.
Six tables. file_placements is the desktop itself: one row per tile, with
its position and a polymorphic reference (file_type + file_ref) to
whatever the tile stands for. Dotted lines are those polymorphic references;
solid lines are plain id columns.
erDiagram
wp_users ||--o{ desktop_mode_stored_files : "owner_id"
wp_users ||--o{ desktop_mode_folders : "owner_id, trashed_by"
wp_users ||--o{ desktop_mode_file_placements : "owner_id, trashed_by"
wp_users ||--o{ desktop_mode_folder_shares : "invited_by"
wp_users ||--o{ desktop_mode_share_user_decisions : "user_id"
desktop_mode_folders |o--o{ desktop_mode_file_placements : "parent_id (0 = desktop)"
desktop_mode_folders |o--o{ desktop_mode_file_placements : "trashed_via_folder"
desktop_mode_file_placements }o..o| desktop_mode_folders : "file_ref when file_type = folder"
desktop_mode_file_placements }o..o| desktop_mode_stored_files : "file_ref when file_type = upload"
desktop_mode_file_placements }o..o| wp_posts : "file_ref when file_type = post / attachment"
desktop_mode_file_placements }o..o| wp_users : "file_ref when file_type = user"
desktop_mode_folders ||--o{ desktop_mode_folder_shares : "folder_id when target_type = folder"
desktop_mode_stored_files ||--o{ desktop_mode_folder_shares : "folder_id when target_type = file"
desktop_mode_folder_shares ||--o{ desktop_mode_share_user_decisions : "share_id"
wp_posts }o..o| desktop_mode_stored_files : "_openstation_stored_file_id (attachment created from an upload)"
desktop_mode_stored_files {
bigint id PK
bigint owner_id FK
varchar display_name
varchar disk_name "UUID, no extension; bytes on disk"
bigint size_bytes
varchar mime
bigint created_at_ms
bigint updated_at_ms
}
desktop_mode_folders {
bigint id PK
bigint owner_id FK
varchar name
varchar share_mode "diagnostic only"
longtext share_meta "diagnostic only"
bigint updated_at_ms
bigint trashed_at_ms "NULL = active"
bigint trashed_by FK
longtext trashed_meta
}
desktop_mode_file_placements {
bigint id PK
bigint owner_id FK
bigint parent_id FK "0 = desktop root"
varchar file_type "folder, upload, post, attachment, user, shortcut"
varchar file_ref "id of the referenced row"
int x
int y
int sort_order
longtext meta
bigint trashed_at_ms "NULL = active"
bigint trashed_by FK
bigint trashed_via_folder FK
longtext trashed_meta
}
desktop_mode_file_tombstones {
bigint id PK
varchar kind "post, media, user, agent, chat, comment, drag"
bigint ref_id "id of the deleted row"
bigint removed_at_ms
}
desktop_mode_folder_shares {
bigint id PK
varchar target_type "folder or file"
bigint folder_id FK "folders.id or stored_files.id"
varchar principal_type "user or role"
varchar principal_ref "user id or role slug"
varchar capability "read or write"
varchar state "pending, accepted, ..."
bigint invited_by FK
bigint invited_at_ms
bigint decided_at_ms
}
desktop_mode_share_user_decisions {
bigint id PK
bigint share_id FK
bigint user_id FK
varchar state
bigint decided_at_ms
}
file_tombstones has no edges on purpose: it records ids of rows deleted
outside the plugin (a post, an attachment, a user, an agent, a chat, a
comment) so clients can drop the tiles that pointed at them.
The two custom post types, the agent rows in wp_users, and the two Games
tables.
erDiagram
wp_users ||--o{ wp_usermeta : "user_id"
wp_users ||--o{ wp_posts : "post_author"
wp_posts ||--o{ wp_postmeta : "post_id"
wp_posts ||--o{ wp_comments : "comment_post_ID"
wp_comments ||--o{ wp_commentmeta : "comment_id"
wp_posts }o..o| wp_users : "_desktop_mode_agent_chat_agent_id (chat to its agent)"
wp_users ||--o{ desktop_mode_game_scores : "user_id"
wp_users ||--o{ desktop_mode_game_challenges : "challenger_id"
wp_users ||--o{ desktop_mode_game_challenges : "recipient_id"
wp_users {
bigint ID PK
varchar user_login
varchar user_email
varchar display_name "agents: _desktop_mode_agent = 1 in usermeta"
}
wp_posts {
bigint ID PK
bigint post_author FK
varchar post_type "desktop_mode_chat, wpd_note, plus Core types"
varchar post_status
}
desktop_mode_game_scores {
bigint id PK
varchar game
bigint user_id FK
bigint score
longtext meta
bigint created_at_ms
}
desktop_mode_game_challenges {
bigint id PK
varchar game
bigint challenger_id FK
bigint recipient_id FK
bigint score_to_beat
longtext score_meta
varchar state "pending, accepted, declined, completed"
varchar result
bigint result_score
longtext result_meta
bigint created_at_ms
bigint decided_at_ms
bigint completed_at_ms
bigint updated_at_ms "Heartbeat high-water mark"
}
A module per row, a store per column. Read down a column to answer "who writes to options?"; read along a row to see everything a module touches. The sections below name the exact tables and keys.
| Module | Own tables |
wp_users + usermeta |
wp_posts + postmeta |
wp_commentmeta |
wp_options |
Transients | Disk |
|---|---|---|---|---|---|---|---|
| Files | ● | ● | ● | ● | ● | ||
| Folder sharing | ● | ||||||
| Games | ● | ● | ● | ||||
| Agents | ● | ● | ● | ● | |||
| Notes | ● | ● | ● | ||||
| Recycle Bin | ● | ● | ● | ||||
| Presence | ● | ||||||
| Preferences and session | ● | ● | |||||
App Framework Store
|
● | ● | |||||
| Desktop themes | ● | ● | |||||
| Media Library | ● | ● | |||||
| AI Copilot | ● | ● | |||||
| OAuth relay | ● | ||||||
| PWA | ● | ● | |||||
| Content changes feed | ● | ||||||
| Caches: content graph, Woo, feeds, stats | ● | ● | |||||
| Migrations | ● |
Six belong to Files (desktop tiles and sharing) and two to Games. All are
created on activation and, if missing, lazily on init, admin_init and
rest_api_init whenever the schema-version option does not match the
constant (includes/desktop-files/schema.php, includes/games/schema.php).
Timestamps are epoch milliseconds in BIGINT columns, not DATETIME.
| Table | Module | What it holds | Created by |
|---|---|---|---|
desktop_mode_stored_files |
Files | One uploaded file per row. The bytes live at uploads/desktop-mode-files/{owner_id}/{disk_name}; the table is the only source of name, size and MIME. |
dbDelta |
desktop_mode_folders |
Files | Desktop folders. share_mode and share_meta are diagnostic leftovers; visibility is computed from folder_shares. |
dbDelta |
desktop_mode_file_placements |
Files | One row per desktop tile: position (x, y, sort_order), parent folder (0 = desktop root) and what the tile stands for. file_type + file_ref is polymorphic: folder, upload, post, attachment, user, shortcut. The trashed_* columns carry the Recycle Bin. |
dbDelta |
desktop_mode_file_tombstones |
Files | Ids of rows deleted outside the plugin (posts, media, users, agents, chats, comments) so clients drop orphaned tiles. | dbDelta |
desktop_mode_folder_shares |
Folder sharing | Access grants. target_type decides whether folder_id names a folder or a stored file; principal_type + principal_ref is a user id or a role slug. |
CREATE TABLE IF NOT EXISTS |
desktop_mode_share_user_decisions |
Folder sharing | Each user's accept / decline of a grant (unique per share + user). | CREATE TABLE IF NOT EXISTS |
desktop_mode_game_scores |
Games | One row per finished play: game, user, score, free-form meta. | dbDelta |
desktop_mode_game_challenges |
Games | A challenge between two users with its state machine and result. | dbDelta |
The two sharing tables deliberately bypass dbDelta: its DESCRIBE-based
detection can miss an existing table on some MySQL / MariaDB setups and
then issue a bare CREATE TABLE that fails. Their ensure_* helpers check
INFORMATION_SCHEMA and use CREATE TABLE IF NOT EXISTS; the schema file
explains the history. Multisite gets one set of tables per site through
$wpdb->prefix.
post_type |
Module | What it is | Meta on the post |
|---|---|---|---|
desktop_mode_chat |
Agents | One conversation with an AI agent. The post belongs to the human; the agent is in meta. |
_desktop_mode_agent_chat_agent_id → wp_users.ID of the agent |
wpd_note |
Notes | One sticky note on the desktop. |
_wpd_note_x, _wpd_note_y, _wpd_note_z, _wpd_note_color, _wpd_note_seed, _wpd_note_converted_post → wp_posts.ID
|
Keys with a leading underscore are private (hidden from REST and the
profile screen). AI agents are ordinary wp_users rows flagged with
_desktop_mode_agent, so their whole profile lives here too.
meta_key |
Module | Content |
|---|---|---|
desktop_mode_mode |
Core | The user's opt-in: 1 turns the shell on. |
desktop_mode_os_settings |
Preferences | Every OpenStation Preferences value (appearance, windows, navigation, features). REST-synced through /wp-json/desktop-mode/v1/os-settings. |
desktop_mode_session |
Session | Open windows and their geometry for restore. On multisite the key is suffixed: _{blog_id} on a secondary site, _network in the network admin. |
desktop_mode_default_window |
Core | The window that opens on arrival. |
desktop_mode_file_associations |
Files | Which app opens each file type. |
desktop_mode_pwa_state |
PWA | Install / prompt state. |
desktop_mode_seen_intros |
Onboarding | Intros already shown. |
desktop_mode_rebrand_notice |
Onboarding | Rebrand notice dismissed. |
desktop_mode_game_playtime |
Games | Lifetime play time per game. |
desktop_mode_game_playtime_days |
Games | Play time per day (rolling window). |
openstation_station_home_card_preferences |
Station Home | Which home cards are shown or hidden. |
openstation_app_store |
App Framework | The Store contract with user scope, a key → value map. Plugins stores desktop-mode-plugins:installed-view here (cards or table, default cards). The same name with site scope is an option. |
_desktop_mode_last_login_at |
Users | Last login time. |
_desktop_mode_has_notes |
Notes | Cache `{rev}:{0 |
_desktop_mode_agent |
Agents |
1 marks this wp_users row as an AI agent rather than a person. |
_desktop_mode_agent_abilities, _desktop_mode_agent_created_by (→ wp_users.ID), _desktop_mode_agent_description, _desktop_mode_agent_face, _desktop_mode_agent_face_seed, _desktop_mode_agent_instructions, _desktop_mode_agent_model, _desktop_mode_agent_rate_limit, _desktop_mode_agent_runs, _desktop_mode_agent_triggers, _desktop_mode_agent_vibes
|
Agents | The agent's profile and run log, on its own user row. includes/agents/store.php owns every key. |
meta_key |
On | Content |
|---|---|---|
_desktop_mode_trash_user_id |
posts, comments | Who sent the row to the Recycle Bin (→ wp_users.ID). |
_desktop_mode_trash_time_gmt |
posts, comments | When it was sent there. |
_desktop_mode_width, _desktop_mode_height
|
attachments | Cached image dimensions for the Media Library; a one-time backfill is flagged by the desktop_mode_media_dims_backfilled option. |
_openstation_stored_file_id |
attachments | The stored file this attachment was created from (→ desktop_mode_stored_files.id). |
_openstation_stored_file_key |
attachments | Deduplication key of that stored file. |
_desktop_mode_ai_analysis |
comments | Result of the AI moderation pass. |
option_name |
Module | Content |
|---|---|---|
desktop_mode_files_schema_version |
Files | Installed schema version; a mismatch triggers the lazy install. |
desktop_mode_games_schema_version |
Games | Same, for the two Games tables. |
desktop_mode_migration_version |
Migrations | Last data migration applied (includes/migrations.php). |
desktop_mode_extended_options |
Preferences | Site-wide extended options: Media Library enhancement, Games, AI agents, OpenStation Network, plus window_prewarm and admin_asset_cache (both default true; administrator opt-outs apply on shell reload). |
desktop_mode_desktop_themes |
Desktop themes | Themes uploaded as ZIPs and the active selection; their files go to uploads/desktop-mode-themes/. |
desktop_mode_comments_ai_moderation |
AI Copilot | Whether comment moderation by AI is on. |
desktop_mode_agents_defaults_seeded |
Agents | Flag: default agents already created. |
desktop_mode_media_dims_backfilled |
Media | Flag: dimensions backfill done. |
desktop_mode_notes_rev |
Notes | Global notes revision; invalidates the per-user cache. |
desktop_mode_terms_cache_version |
Content graph | Cache version for terms. |
_desktop_mode_presence |
Presence | Snapshot of every user's presence (autoload = no). Heartbeat updates it with a write throttle; a daily cron prunes it. |
_desktop_mode_content_changes_log |
Content changes | Recent content changes for the feed, capped at 100 entries (autoload = no). |
_desktop_mode_recycle_bin_change_ts |
Recycle Bin | Timestamp of the last bin change, for the badge (autoload = no). |
openstation_app_store |
App Framework | The Store contract with site scope. |
Caches with an expiry. They live in wp_options under the _transient_
prefix unless a persistent object cache is installed. None is a source of
truth; every one regenerates.
| Prefix | Module | Content |
|---|---|---|
desktop_mode_cg3_* |
Content graph | The computed content graph. |
desktop_mode_oauth_state_* |
Auth | State of an OAuth flow in progress. |
desktop_mode_woo_customer_plan, _customer_spend, _product_total, _product_plan, _coupon_plan
|
WooCommerce | Customer, product and coupon aggregates read from Woo's wc_orders, woocommerce_order_items and woocommerce_order_itemmeta. |
dm_user_insights_{id} |
User edit | Computed insights for one user; cleared when the account is edited. |
desktop_mode_about_feed_v1, _failure_v1, _stale_v1
|
About | The news feed and its failure / stale states. |
desktop_mode_living_tree_snapshot |
Living Tree | Wallpaper snapshot. |
desktop_mode_site_views_meta |
Stats | Site views metadata. |
openstation_shell_build |
PWA | Hash of the shell bundles, used to detect a deploy. |
dm_pwsz_map |
Plugins | On-disk size of each plugin. |
The Plugins app also uses the desktop-mode-plugins object-cache group for
in-request caching.
| Resource | Kind | Use |
|---|---|---|
uploads/desktop-mode-files/{owner_id}/{disk_name} |
Disk | Bytes of each stored file; disk_name is an extension-less UUID. |
uploads/desktop-mode-themes/ |
Disk | Uploaded desktop themes. |
uploads/desktop-mode-agent-faces/ |
Disk | Generated agent face images. |
desktop_mode_files_daily_prune |
Cron (daily) | Sweeps ZIP temp files and reconciles stored files against the disk. |
desktop_mode_presence_daily_prune |
Cron (daily) | Prunes the presence option. |
desktop-mode-widgets-geometry, desktop-mode/files, … |
localStorage |
Widget geometry and other client state; never reaches the database. |
- Files on the Desktop — the registry and the placements model from the plugin author's side.
-
Folder Sharing — the ACL model on top of
folder_sharesandshare_user_decisions. - Architecture — preference and session persistence in detail.
- Multisite — session scoping across a network.
All names below are per-site. No transient or browser cache is the source of truth for job execution.
| Name | Kind | Contents / lifetime |
|---|---|---|
openstation_agent_job_{uuid} |
Option, non-autoloaded | Owner, agent, bounded message/history, source, timestamps, status, result/error. Retained for one day. |
openstation_agent_job_claim_{uuid} |
Option, non-autoloaded | Atomic execution claim, retained with the job; never recycled for retries. |
openstation_agent_job_active_{owner}_{agent} |
Option, non-autoloaded | Admission slot (`uuid |
openstation_agent_job_run |
Single cron event, UUID argument | Executes a queued invocation once. |
openstation_agent_job_cleanup |
Single cron event, UUID argument | Deletes job input/result and its claim after one day. |
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