Status: Experimental feature plugin
Storage layer for Gutenberg's real-time collaborative editing.
Gutenberg's real-time collaboration needs somewhere to keep two kinds of data: ephemeral awareness (who's in the room, cursor position) and a persistent log of CRDT updates for each document. Storing either as post meta means every write invalidates post caches site-wide (#64696). This plugin implements Gutenberg's WP_Sync_Storage interface to keep both out of post meta entirely: awareness is delegated to Presence API's wp_presence table, and CRDT updates go into a dedicated wp_collaboration table.
git clone https://github.com/WordPress/sync-storage.git
cd sync-storage
npm install
npm run env:startThen open localhost:8888/wp-admin/ (admin / password).
The first run builds Gutenberg from trunk, since the __unstable_wp_sync_storage filter this plugin hooks hasn't shipped in a tagged Gutenberg release yet. That takes a few minutes; subsequent runs reuse the build.
Awareness
- Gutenberg calls
set_awareness_state( $room, $awareness ) - Each entry is forwarded to Presence API's
wp_set_presence() - Reads go through
get_awareness_state( $room ), which callswp_get_presence()and reshapes the result into Gutenberg's expected format
CRDT updates
- Gutenberg calls
add_update( $room, $update ) - The update is inserted into
wp_collaborationas an opaque, JSON-encoded row - Gutenberg polls
get_updates_after_cursor( $room, $cursor )to fetch anything new remove_updates_before_cursor()deletes compacted rows once Gutenberg confirms they're no longer needed
Both paths validate that the current user can edit_post the room's underlying post before touching storage.
| Pattern | Example |
|---|---|
postType/{type}:{id} |
postType/post:42 |
This plugin implements Gutenberg's WP_Sync_Storage interface. These are the methods Sync_Storage_Provider provides; there's no separate global-function API like Presence API's.
// Read awareness state for a room, reshaped from Presence API's format.
$entries = $storage->get_awareness_state( $room );
// Write each client's awareness state, delegated to wp_set_presence().
$storage->set_awareness_state( $room, $awareness );
// Append an opaque CRDT update to wp_collaboration.
$storage->add_update( $room, $update );
// Last update id returned to this request for a room (0 if none yet).
$cursor = $storage->get_cursor( $room );
// Number of stored updates for a room.
$count = $storage->get_update_count( $room );
// Updates with id > $cursor, ordered by id.
$updates = $storage->get_updates_after_cursor( $room, $cursor );
// Delete compacted updates with id < $cursor.
$storage->remove_updates_before_cursor( $room, $cursor );| Column | Type | Purpose |
|---|---|---|
| id | BIGINT UNSIGNED | Auto-increment cursor for polling |
| room | VARCHAR(191) | Room identifier, e.g. postType/post:42 |
| type | VARCHAR(20) | Reserved for future update classification; always NULL today |
| data | LONGTEXT | JSON-encoded opaque payload |
| timestamp | BIGINT UNSIGNED | Milliseconds since epoch, matching Yjs. Used by cleanup |
Indexes: PRIMARY KEY (id), KEY room_id (room, id) for polling, KEY room_timestamp (room, timestamp) for cleanup.
A daily cron removes rows older than 7 days.
Fired when a room's collaborator count crosses the 1-to-2 threshold, as reported by Presence API. Used internally to flag _sync_storage_active post meta; also available for third-party integrations.
add_action( 'sync_storage_room_active', function ( $post_id, $entries ) {
// A second collaborator just joined $post_id.
}, 10, 2 );
add_action( 'sync_storage_room_inactive', function ( $post_id, $entries ) {
// Back down to a single editor (or none).
}, 10, 2 );Gutenberg's own filter, hooked by this plugin to replace its default post-meta-backed storage with Sync_Storage_Provider. Any plugin can hook this filter to supply a different WP_Sync_Storage implementation (Redis, WebSocket-backed, etc.) without patching Gutenberg.
- WordPress 7.0+
- PHP 7.4+
- Presence API
- Gutenberg trunk (or a future release once
__unstable_wp_sync_storageships stable)
Sponsored by the Core team. Discussion happens in #feature-realtime-collaboration on WordPress Slack and on Trac #64696.
Questions and bug reports: GitHub Issues.
GPL-2.0-or-later