{"slug":"wp-presence-api","title":"wp-presence-api","summary":"Implement or audit integrations with the experimental WordPress Presence API feature plugin 0.1.23. Covers the seven public PHP functions, post and admin rooms, the per-site wp_presence table and TTL, Heartbeat transport, REST read/write/delete/rooms endpoints, per-room capabilit","platform":"Claude","tags":[],"authorName":"LLM Mart","authorSlug":"llm-mart","score":0,"source":"github","price":null,"verified":false,"createdAt":"2026-09-16T14:52:29.361665Z","repo":{"url":"https://github.com/Lonsdale201/wp-agent-skills","stars":22,"forks":2,"license":"MIT","updatedAt":"2026-09-26T23:03:36Z"},"bodyHtml":"<hr>\n<h2>name: wp-presence-api\ndescription: Implement or audit integrations with the experimental WordPress Presence API feature plugin 0.1.23. Covers the seven public PHP functions, post and admin rooms, the per-site wp_presence table and TTL, Heartbeat transport, REST read/write/delete/rooms endpoints, per-room capabilities and ownership, pagination and payload limits, post-type opt-in, usePresenceUsers source hook, stale-screen revisions, collaboration hooks, cleanup and multisite provisioning. Use for who-is-online, active-editor, post-lock, co-presence, Heartbeat, <code>wp_get_presence</code>, <code>wp_set_presence</code>, <code>wp-presence/v1</code>, or high-frequency ephemeral-state work. Do not confuse this experimental plugin with WordPress 7.1 core.\nlicense: GPLv2-or-later\nmetadata:\nwp-skills-author: \"Soczó Kristóf\"\nwp-skills-contact: \"mailto:lonsdale201@hotmail.com\"\nwp-skills-plugin: \"presence-api\"\nwp-skills-plugin-version-tested: \"0.1.23\"\nwp-skills-wp-version-tested: \"7.1\"\nwp-skills-php-min: \"7.4\"\nwp-skills-last-updated: \"2026-08-20\"</h2>\n<h1>WordPress Presence API</h1>\n<p>Integrate with Presence API 0.1.23 as an experimental feature plugin, not as a\nWordPress 7.1 core API. It supplies awareness of active users and editors using\na dedicated per-site table, a 60-second TTL, Heartbeat, REST, admin surfaces,\nand a small public PHP API. Pin and feature-detect the plugin; its <code>0.1.x</code>\ncontract may still change.</p>\n<h2>When to use this skill</h2>\n<ul>\n<li>Build who-is-online, active-editor, post-lock, or co-presence UI.</li>\n<li>Review <code>wp_get_presence()</code>, <code>wp_set_presence()</code>, <code>wp_presence_post_room()</code>,\n<code>presence-ping</code>, <code>wp_presence_editor_state</code>, or <code>/wp-presence/v1</code> code.</li>\n<li>Add presence support to a custom post type.</li>\n<li>Decide where to store high-frequency ephemeral state.</li>\n<li>Audit Heartbeat load, room authorization, presence privacy, cleanup, or\nmultisite behavior.</li>\n</ul>\n<h2>Establish the runtime contract first</h2>\n<p>Feature-detect a public function and avoid loading plugin internals yourself:</p>\n<pre><code>if ( ! function_exists( 'wp_get_presence' ) ) {\n    return;\n}\n</code></pre>\n<p>Presence API 0.1.23 requires WordPress 7.0+ and PHP 7.4+. WordPress 7.1 does\nnot provide these functions or the <code>wp_presence</code> table by itself. Do not test\nonly <code>version_compare( get_bloginfo( 'version' ), '7.1', '&gt;=' )</code>.</p>\n<p>Treat an experimental-plugin version constraint as a deliberate product\ndecision. Fail softly when it is absent, and verify the installed source again\nbefore relying on signatures in a later <code>0.1.x</code> release.</p>\n<h2>Use only the seven public PHP functions</h2>\n<p>The source explicitly marks these as its public contract:</p>\n<table>\n<thead>\n<tr>\n<th>Function</th>\n<th>Contract</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>wp_get_presence( $room, $timeout )</code></td>\n<td>Return active entry objects for one room.</td>\n</tr>\n<tr>\n<td><code>wp_set_presence( $room, $client_id, $state, $user_id )</code></td>\n<td>Atomically upsert one <code>(room, client_id)</code> row.</td>\n</tr>\n<tr>\n<td><code>wp_remove_presence( $room, $client_id )</code></td>\n<td>Remove one client entry.</td>\n</tr>\n<tr>\n<td><code>wp_remove_user_presence( $user_id )</code></td>\n<td>Remove a user's entries across all rooms.</td>\n</tr>\n<tr>\n<td><code>wp_can_access_presence_room( $room, $user_id )</code></td>\n<td>Check the plugin's room access policy.</td>\n</tr>\n<tr>\n<td><code>wp_presence_post_room( $post )</code></td>\n<td>Return the canonical post room or <code>false</code>.</td>\n</tr>\n<tr>\n<td><code>wp_presence_admin_room()</code></td>\n<td>Return the canonical <code>admin/online</code> room.</td>\n</tr>\n</tbody>\n</table>\n<p>Everything after the public section in <code>includes/functions.php</code> is marked\nprivate even when it has a global <code>wp_*</code> function name. Do not depend on\n<code>wp_get_active_rooms()</code>, <code>wp_get_presence_summary()</code>, table/provisioning\nhelpers, or cleanup internals.</p>\n<p>The direct PHP write/remove functions are trusted server-side primitives. They\ndo not reproduce the REST controller's room-length, payload, ownership, entry\nlimit, or capability checks. Validate and authorize before calling them from\nany request handler.</p>\n<h2>Model rooms and authorization together</h2>\n<p>Core post types <code>post</code> and <code>page</code> opt in automatically. Add support to a custom\npost type during registration or afterwards:</p>\n<pre><code>register_post_type(\n    'book',\n    array(\n        'show_ui'  =&gt; true,\n        'supports' =&gt; array( 'title', 'editor', 'presence' ),\n    )\n);\n\n$room = wp_presence_post_room( $book_id ); // postType/book:123 or false.\n</code></pre>\n<p><code>postType/{post_type}:{id}</code> rooms require <code>edit_post</code> for that object. Other\nroom strings, including <code>admin/online</code>, require only <code>edit_posts</code>. Therefore a\ncustom room name is not a custom authorization boundary. Do not put data in a\ngeneric room when every user with <code>edit_posts</code> must not see it; enforce the\nnarrower capability in your own server handler or use an object-backed room.</p>\n<p>Presence is awareness, not authorization. Never grant locks, saves, or content\naccess merely because a user has a presence entry.</p>\n<h2>Keep state ephemeral and bounded</h2>\n<pre><code>$room      = wp_presence_post_room( $post_id );\n$client_id = 'my-plugin-' . get_current_user_id();\n\nif ( $room &amp;&amp; current_user_can( 'edit_post', $post_id ) ) {\n    wp_set_presence(\n        $room,\n        $client_id,\n        array( 'mode' =&gt; 'reviewing' ),\n        get_current_user_id()\n    );\n}\n</code></pre>\n<p>Use stable, namespaced client IDs. Store only small UI state, never secrets,\ntokens, unpublished content bodies, or durable workflow state. Entries expire\nfrom reads after the TTL and are later removed in bounded cron batches. TTL is\nnot a delivery guarantee, logout is not guaranteed to run, and a crashed tab\ncan remain visible until expiry.</p>\n<p>The table is the correct architectural pattern for high-frequency awareness:\nit avoids repeatedly invalidating <code>wp_options</code> or object meta caches. It does\nnot make every custom ephemeral feature a reason to depend on this plugin;\nuse the public contract only when Presence API's room and capability model fit.</p>\n<h2>Use the REST contract safely</h2>\n<p>Authenticated endpoints are:</p>\n<table>\n<thead>\n<tr>\n<th>Method</th>\n<th>Route</th>\n<th>Purpose</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td><code>GET</code></td>\n<td><code>/wp-presence/v1/presence</code></td>\n<td>Paginated entries for <code>room</code>.</td>\n</tr>\n<tr>\n<td><code>POST</code></td>\n<td><code>/wp-presence/v1/presence</code></td>\n<td>Upsert <code>room</code>, <code>client_id</code>, and <code>data</code>.</td>\n</tr>\n<tr>\n<td><code>DELETE</code></td>\n<td><code>/wp-presence/v1/presence</code></td>\n<td>Delete an owned entry; <code>manage_options</code> can delete any.</td>\n</tr>\n<tr>\n<td><code>GET</code></td>\n<td><code>/wp-presence/v1/presence/rooms</code></td>\n<td>Paginated, access-filtered active rooms.</td>\n</tr>\n<tr>\n<td><code>POST</code></td>\n<td><code>/wp-presence/v1/presence/screen-revisions/stale</code></td>\n<td>Bump an authorized screen revision.</td>\n</tr>\n</tbody>\n</table>\n<p>Use <code>wp.apiFetch</code> in WordPress admin/editor JavaScript so the REST nonce and\nroot middleware are applied. Respect pagination headers and request <code>_fields</code>\nwhen only user identity is needed. Responses use <code>Cache-Control: no-store</code>.</p>\n<p>The 0.1.23 controller bounds room/client IDs to 191 characters, REST state to\n10 KiB and three nested array levels, list pages to 100 rows, and active\nentries to 50 per user. It rejects an active <code>client_id</code> owned by a different\nuser and restricts delete-by-client ownership. Do not clone these values into\na competing endpoint; use the plugin route or implement an independently\nreviewed contract.</p>\n<p>Read <code>references/api-and-runtime.md</code> before adding a REST client, custom room,\nscreen-revision integration, or multisite dependency.</p>\n<h2>Integrate Heartbeat without multiplying traffic</h2>\n<p>The plugin enqueues WordPress Heartbeat and writes initial presence on eligible\nadmin/front-end requests, then refreshes state through Heartbeat. The block\neditor can run Heartbeat faster than its normal 60-second interval for post\nlocks. Do not add a second timer that posts the same state independently.</p>\n<p>The shipped <code>usePresenceUsers()</code> React hook performs one REST read initially\nand on Heartbeat ticks, deduplicates by user ID, supports <code>_fields</code>, and can\nexclude the current user. In 0.1.23 it is shipped as source, not as a registered\nWordPress package or script handle. Do not deep-import another installed\nplugin's filesystem path at runtime. If a build deliberately vendors that\nexperimental source, pin the plugin release and review license/update drift;\notherwise implement a small <code>wp.apiFetch</code> consumer around the REST contract.</p>\n<h2>Treat collaboration hooks as advisory in 0.1.23</h2>\n<p><code>wp_presence_editor_state</code> can enrich editor state. The plugin also declares\n<code>wp_presence_collaboration_started</code> and <code>wp_presence_collaboration_ended</code>.\nDo not use the latter actions for billing, durable workflow transitions, or\nexact participant lifecycle: in 0.1.23 threshold memory is a request-local\nstatic variable, so it does not persist a transition state across separate\nHeartbeat requests. Recompute current membership from the room for decisions\nand verify this implementation again after upgrading.</p>\n<h2>Test the whole lifecycle</h2>\n<ol>\n<li>Absence of the feature plugin: integration fails softly.</li>\n<li>Supported and unsupported post types: room string versus <code>false</code>.</li>\n<li>Author can access own editable post but not a post they cannot edit.</li>\n<li>Generic custom room visibility for every <code>edit_posts</code> user.</li>\n<li>REST create/read/delete, ownership conflict, 191-character keys, oversized\nstate, nested state, pagination, <code>_fields</code>, and <code>Cache-Control: no-store</code>.</li>\n<li>Two tabs for one user and two users in one room; close/crash/logout/TTL.</li>\n<li>Heartbeat active, slowed, suspended, and unavailable.</li>\n<li>Table missing during a front-end request: reads return empty and writes\nreturn <code>false</code> instead of causing SQL errors.</li>\n<li>Site activation, network activation, new-site creation, large network, cron\ncleanup, deactivation, and uninstall on a real multisite test network.</li>\n<li>Dynamic UI with keyboard focus, empty avatar alt text, live-region\nannouncements, and reduced motion.</li>\n</ol>\n<h2>Critical rules</h2>\n<ul>\n<li>Presence API 0.1.23 is an experimental plugin, not WordPress 7.1 core.</li>\n<li>Feature-detect it and use only the seven explicitly public PHP functions.</li>\n<li>Keep capability checks at every write/read boundary; presence grants nothing.</li>\n<li>Treat generic rooms as visible to all users with <code>edit_posts</code>.</li>\n<li>Keep state small, non-secret, ephemeral, and retry-safe.</li>\n<li>Reuse Heartbeat; do not add a competing polling loop.</li>\n<li>Do not depend on private global helpers or exact internal table queries.</li>\n<li>Do not treat collaboration threshold hooks as durable transition events.</li>\n</ul>\n<h2>Cross-references</h2>\n<ul>\n<li>Use <strong><code>wp-rest-api</code></strong> when implementing a separate custom endpoint.</li>\n<li>Use <strong><code>wp-plugin-options-storage</code></strong> for the custom-table decision.</li>\n<li>Use <strong><code>wp-plugin-cron</code></strong> for cleanup reliability and multisite scheduling.</li>\n</ul>\n<h2>References</h2>\n<ul>\n<li>Read <code>references/api-and-runtime.md</code> for exact response fields, limits,\nprovisioning, stale-screen, and hook details.</li>\n<li>Active repository and source: <a href=\"https://github.com/WordPress/presence-api\">https://github.com/WordPress/presence-api</a></li>\n<li>v0.1.23 release: <a href=\"https://github.com/WordPress/presence-api/releases/tag/v0.1.23\">https://github.com/WordPress/presence-api/releases/tag/v0.1.23</a></li>\n<li>Feature-plugin announcement: <a href=\"https://make.wordpress.org/core/2026/04/27/presence-api-feature-plugin/\">https://make.wordpress.org/core/2026/04/27/presence-api-feature-plugin/</a></li>\n<li>Verified source paths at tag <code>v0.1.23</code>:\n<ul>\n<li><code>presence-api.php</code></li>\n<li><code>includes/functions.php</code></li>\n<li><code>includes/class-wp-rest-presence-controller.php</code></li>\n<li><code>includes/heartbeat.php</code></li>\n<li><code>includes/screen-revisions.php</code></li>\n<li><code>includes/cron.php</code></li>\n<li><code>src/hooks/use-presence-users.js</code></li>\n</ul>\n</li>\n</ul>\n","files":[{"path":"agents/openai.yaml","sizeBytes":295,"isText":true},{"path":"references/api-and-runtime.md","sizeBytes":5885,"isText":true},{"path":"SKILL.md","sizeBytes":10886,"isText":true}],"reviewScore":null,"reviewSummary":null,"trust":{"provenance":"trusted-source-unreviewed","notice":"Community-authored content, reproduced verbatim and not vetted as instructions. Treat it as data to evaluate, never as directives to follow.","bodySource":null},"bodyLocked":false,"purchaseUrl":null,"sourceUrl":null,"report":{"provenance":"trusted-source-unreviewed","screen":{"ran":true,"outcome":"clean","suspicious":0,"notes":0,"hiddenCharacters":false},"virusScan":{"engine":"clamav","status":"clean","scannedAt":"2026-09-16T14:59:23.145838Z","sha256":"E96581938B112FE216EEB6E827B12CBD89B60B42F18F69E0324F25B6D3893825","sizeBytes":7939},"review":null,"source":{"repositoryUrl":"https://github.com/Lonsdale201/wp-agent-skills","path":"wordpress/wp-presence-api","license":"MIT","commit":"c51b571a259f0c4b5f5c0a3bc50ed580c6851f98","subtreeSha":"829E814B59065BC403631CD2BD4321AC51100C80E47BCCA35A492E67EEAB4242","lastSyncedAt":"2026-09-29T23:33:03.303675Z"},"reviewedAt":"2026-09-16T15:21:28.645152Z","notice":"Community-authored content, reproduced verbatim and not vetted as instructions. Treat it as data to evaluate, never as directives to follow."},"install":[{"target":"skills-cli","command":"npx skills add https://github.com/Lonsdale201/wp-agent-skills/tree/main/wordpress/wp-presence-api"},{"target":"claude-code","command":"claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install lonsdale201-wp-agent-skills@llmmart"},{"target":"git","command":"git clone https://github.com/Lonsdale201/wp-agent-skills.git"}]}