Description
When Real-Time Collaboration is enabled (wp_collaboration_enabled = true), persistCRDTDoc in @wordpress/core-data sends the entire edited entity record back to the server on page load. This causes 400 errors when any field in the record doesn't pass strict REST schema validation — even if the post is perfectly valid and functional.
The issue is in packages/core-data/src/entities.js (built as build/scripts/core-data/index.js around line 5908):
persistCRDTDoc: () => {
resolveSelect2.getEditedEntityRecord(kind, name, key).then((editedRecord) => {
const { meta, status } = editedRecord;
if ("auto-draft" === status || !meta) {
return;
}
dispatch3.saveEntityRecord(
kind,
name,
editedRecord // ← sends the ENTIRE record
);
});
},
saveEntityRecord with a full record hits the non-autosave path, which sends everything as PUT /wp/v2/posts/{id} — including readonly fields, computed fields, and values that the REST API returns on GET but rejects on write.
Step-by-step reproduction instructions
- Enable Real-Time Collaboration (Settings → Writing → "Enable real-time collaboration", or set
wp_collaboration_enabled to 1)
- In Settings → Discussion, uncheck "Allow link notifications from other blogs (pingbacks and trackbacks) on new posts" and save
- Create a new post and publish it
- Open that post in the block editor
- Open browser DevTools → Network tab
- Observe a
POST /wp-json/wp/v2/posts/{id}?_locale=user request returning 400 Bad Request
The error response:
{
"code": "rest_invalid_param",
"message": "Invalid parameter(s): ping_status",
"data": {
"status": 400,
"params": {
"ping_status": "ping_status is not one of open and closed."
}
}
}
This happens because:
- WordPress core saves
default_ping_status as "" (empty string) when the checkbox is unchecked (see related Core ticket below)
- The REST API returns
ping_status: "" on GET
persistCRDTDoc sends it back on POST
- REST schema validation rejects
"" because the enum is ["open", "closed"]
Why this is a Gutenberg bug (independent of the Core data issue)
persistCRDTDoc only needs to persist meta._crdt_document. Sending the entire entity record is:
- Wasteful — unnecessary data over the wire
- Fragile — any plugin registering a REST field with a schema mismatch (e.g. Co-Authors Plus returns objects where taxonomy schema expects integers) will also cause 400 errors
- Incorrect — readonly fields like
guid, generated_slug, class_list should never be sent back to the server
The autosave path in saveEntityRecord already filters to just title, excerpt, content, meta — persistCRDTDoc should do the same, or better yet, only send { meta: { _crdt_document: value } }.
Related
- WordPress Core:
default_ping_status saves as empty string when unchecked (Trac #51842, closed as worksforme in 2024 — this issue now provides a clear reproduction path)
- Co-Authors Plus #1217 — similar REST field schema collision exposed by the same round-trip behavior
Environment info
- WordPress 6.9.4
- Gutenberg 22.8.2
- PHP 8.4
- MariaDB (default NOT NULL VARCHAR behavior converts NULL → empty string)
Please confirm that you have searched existing issues in the repo.
Please confirm that you have tested with all plugins deactivated except Gutenberg.
Please confirm which theme type you used for testing.
Description
When Real-Time Collaboration is enabled (
wp_collaboration_enabled = true),persistCRDTDocin@wordpress/core-datasends the entire edited entity record back to the server on page load. This causes 400 errors when any field in the record doesn't pass strict REST schema validation — even if the post is perfectly valid and functional.The issue is in
packages/core-data/src/entities.js(built asbuild/scripts/core-data/index.jsaround line 5908):saveEntityRecordwith a full record hits the non-autosave path, which sends everything asPUT /wp/v2/posts/{id}— including readonly fields, computed fields, and values that the REST API returns on GET but rejects on write.Step-by-step reproduction instructions
wp_collaboration_enabledto1)POST /wp-json/wp/v2/posts/{id}?_locale=userrequest returning 400 Bad RequestThe error response:
{ "code": "rest_invalid_param", "message": "Invalid parameter(s): ping_status", "data": { "status": 400, "params": { "ping_status": "ping_status is not one of open and closed." } } }This happens because:
default_ping_statusas""(empty string) when the checkbox is unchecked (see related Core ticket below)ping_status: ""on GETpersistCRDTDocsends it back on POST""because the enum is["open", "closed"]Why this is a Gutenberg bug (independent of the Core data issue)
persistCRDTDoconly needs to persistmeta._crdt_document. Sending the entire entity record is:guid,generated_slug,class_listshould never be sent back to the serverThe autosave path in
saveEntityRecordalready filters to justtitle,excerpt,content,meta—persistCRDTDocshould do the same, or better yet, only send{ meta: { _crdt_document: value } }.Related
default_ping_statussaves as empty string when unchecked (Trac #51842, closed as worksforme in 2024 — this issue now provides a clear reproduction path)Environment info
Please confirm that you have searched existing issues in the repo.
Please confirm that you have tested with all plugins deactivated except Gutenberg.
Please confirm which theme type you used for testing.