Skip to content

fix(resource): serialize only writable fields in update payloads#97

Merged
brd6 merged 4 commits into
mainfrom
fix/read-only-property-values
Jul 14, 2026
Merged

fix(resource): serialize only writable fields in update payloads#97
brd6 merged 4 commits into
mainfrom
fix/read-only-property-values

Conversation

@brd6

@brd6 brd6 commented Jul 14, 2026

Copy link
Copy Markdown
Owner

Description

Fixes retrieve-then-update, which never worked against the live API on any version: update payloads carried every hydrated field, and the API rejects several of them.

  • toArrayForUpdate() on Page, Database, and DataSource is now strict on the accepted update keys, instead of force-including them alongside every other non-empty field. This stops read-only top-level metadata leaking into bodies — created_time (which serialized as a date object and always failed validation), created_by, last_edited_*, url, parent — and subsumes the earlier object/id and null icon/cover special-casing.
  • Page payloads additionally drop computed property values the API refuses (created_by, created_time, last_edited_by, last_edited_time, formula, rollup, unique_idverification stays, being writable on wiki pages) and properties hydrated with a null value, whose serialized form carries no usable value ("date": null became "date": []). The filter resolves property names through the same snake_case key transform serialization applies, so camelCase-named properties are covered.
  • Explicitly set values still serialize, including false (archived, is_locked) and 0; the archived: false round-trip behavior from Page object serializes archived: false on create and update, causing Notion API validation error #67 is preserved and still pinned.

Motivation and context

The natural "retrieve, mutate, update" flow failed with confusing validation errors on any real-world page (anything with computed or empty properties) — for example body.properties.Last edited by.title should be defined or body.created_time should be a string.

How has this been tested?

  • New tests: computed property values dropped from update and create payloads (asserted under the transformed key names so they cannot pass vacuously), null-valued properties dropped, writable properties kept; the retrieve-then-update payload-shape test updated to the strict contract with its archived: false assertion intact.
  • Verified live against the Notion API: retrieve-then-update now succeeds for a page (on both 2022-06-28 and 2026-03-11), a database row with a full property set, and a database — all of which failed before the change.
  • An adversarial review pass caught the property-name transform mismatch, fixed and re-verified.
  • Full suite green (218 tests), plus parallel-lint, phpcs, PHPStan, and Psalm.

Known remaining edge (separate issue): a data source schema round-trip still fails on computed property configurations, and camelCase-named properties are renamed by the payload key transform — both pre-existing and tracked separately.

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)

PR checklist

  • My change requires a change to the documentation.
  • I have updated the documentation accordingly.
  • I have read the CONTRIBUTING.md document.
  • I have added tests to cover my changes.

brd6 added 4 commits July 14, 2026 22:45
Update payloads carried every hydrated field: read-only top-level
metadata (created_time serialized as a date object, created_by, url),
computed property values the API refuses (formula, rollup,
created/edited metadata, unique_id), and null-valued properties
serialized as empty arrays. Any of these fails validation, so updating
a retrieved page, database row, or database never worked against the
live API on any version.

toArrayForUpdate() is now strict on the accepted keys — subsuming the
earlier object/id and null icon/cover handling — and pages drop
computed property values and properties whose serialized form carries
no value. Explicitly set values, including false, still serialize.
Serialization snake_cases array keys including property names, so the
filter's lookups by original name missed camelCase-named properties
and the reviewer-found leak persisted for them. The filter now derives
the serialized key with the same transform, and the tests assert the
transformed names so they can no longer pass vacuously.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant